diff --git a/.gitignore b/.gitignore index 71bc26627e..50dd5a3dce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,33 +1,34 @@ +compile_commands.json *.orig *.rej __pycache__ *.egg-info *.trace build qtcreator-build *.kdev4 *~ .kateconfig CMakeLists.txt.user* .directory *.autosave *.swp .gdb_history .kdev_include_paths *.config *.creator *.creator.user *.files *.includes .DS_Store *.kate-swap .idea GTAGS GPATH GRTAGS GSYMS BROWSE *.kate-swp /po/ build_dir .flatpak-builder diff --git a/3rdparty/README.md b/3rdparty/README.md index 25675c7f48..0e0f5db025 100644 --- a/3rdparty/README.md +++ b/3rdparty/README.md @@ -1,269 +1,271 @@ # CMake external projects to build krita's dependencies on Linux, Windows or OSX If you need to build Krita's dependencies for the following reasons: * you develop on Windows and aren't using build-tools/windows/build.cmd or KDE's craft * you develop on OSX and aren't using the scripts in krita/packaging/osx or Homebrew * you want to build a generic, distro-agnostic version of Krita for Linux and aren't using the scripts in packaging/linux/appimage * you develop on Linux, but some dependencies aren't available for your distribution and aren't using the scripts in packaging/linux/appimage and you know what you're doing, you can use the following guide to build the dependencies that Krita needs. If you develop on Linux and your distribution has all dependencies available, YOU DO NOT NEED THIS GUIDE AND YOU SHOULD STOP READING NOW Otherwise you risk major confusion. ## Prerequisites Note: on all operating systems the entire procedure is done in a terminal window. 1. git: https://git-scm.com/downloads. Make sure git is in your path 2. CMake 3.3.2 or later: https://cmake.org/download/. Make sure cmake is in your path. * CMake 3.9 does not build Krita properly at the moment, please use 3.8 or 3.10 instead. 3. Make sure you have a compiler: * Linux: gcc, minimum version 4.8 * OSX: clang, you need to install xcode for this * Windows: mingw-w64 7.3 (by mingw-builds): https://sourceforge.net/projects/mingw-w64/ * The Files can be found under "Toolchains targetting Win64/Win32"/"Personal Builds". * For threading, select posix. * For exceptions, select seh (64-bit) or dwarf (32-bit). * Install mingw to something like C:\mingw; the full path must not contain any spaces. * Make sure mingw's bin folder is in your path. It might be a good idea to create a batch file which sets the path and start cmd. * MSVC is *not* supported at the moment. 4. On Windows, you will also need a release of Python 3.6 (*not* 3.7 or any other versions): https://www.python.org. Make sure to have that version of python.exe in your path. This version of Python will be used for two things: to configure Qt and to build the Python scripting module. Make sure that this version of Python comes first in your path. Do not set PYTHONHOME or PYTHONPATH. * Make sure that your Python will have the correct architecture for the version you are trying to build. If building for 32-bit target, you need the 32-bit release of Python. 5. On Windows, if you want to compile Qt with ANGLE support, you will need to install Windows 10 SDK and have the environment variable `WindowsSdkDir` set to it (typically `C:\Program Files (x86)\Windows Kits\10`) ## Setup your environment ## Prepare your directory layout 1. Make a toplevel build directory, say $HOME/dev or c:\dev. We'll refer to this directory as BUILDROOT. You can use a variable for this, on WINDOWS %BUILDROOT%, on OSX and Linux $BUILDROOT. You will have to replace a bare BUILDROOT with $BUILDROOT or %BUILDROOT% whenever you copy and paste a command, depending on your operating system. 2. Checkout krita in BUILDROOT ``` cd BUILDROOT git clone git://anongit.kde.org/krita.git ``` 3. Create the build directory ``` mkdir BUILDROOT/b ``` 4. Create the downloads directory ``` mkdir BUILDROOT/d ``` 5. Create the install directory ``` mkdir BUILDROOT/i ``` ## Prepare the externals build 1. Enter the BUILDROOT/b directory 2. Run cmake: * Linux: ``` export PATH=$BUILDROOT/i/bin:$PATH export PYTHONHOME=$BUILDROOT/i (only if you want to build your own python) cmake ../krita/3rdparty \ -DINSTALL_ROOT=$BUILDROOT/i \ -DEXTERNALS_DOWNLOAD_DIR=$BUILDROOT/d \ -DCMAKE_INSTALL_PREFIX=BUILDROOT/i ``` * OSX: ``` export PATH=$BUILDROOT/i/bin:$PATH export PYTHONHOME=$BUILDROOT/i (only if you want to build your own python) cmake ../krita/3rdparty/ \ -DCMAKE_INSTALL_PREFIX=$BUILDROOT/i \ -DEXTERNALS_DOWNLOAD_DIR=$BUILDROOT/d \ -DINSTALL_ROOT=$BUILDROOT/i ``` * Windows 32-bit / 64-bit: Note that the cmake command needs to point to your BUILDROOT like /dev/d, not c:\dev\d. ``` set PATH=%BUILDROOT%\i\bin\;%BUILDROOT%\i\lib;%PATH% cmake ..\krita\3rdparty -DEXTERNALS_DOWNLOAD_DIR=/dev/d -DINSTALL_ROOT=/dev/i -G "MinGW Makefiles" ``` - If you want to build Qt and some other dependencies with parallel jobs, add `-DSUBMAKE_JOBS=` to this cmake command where is the number of jobs to run (if your PC has 4 CPU cores, you might want to set it to 5). For other jobs, you might need to manually add a -- -j N option, where N is the number of jobs. - If you don't have Windows 10 SDK and don't want to build Qt with ANGLE, add `-DQT_ENABLE_DYNAMIC_OPENGL=OFF` to the CMake command line args. 3. Build the packages: On Windows: ``` cmake --build . --config RelWithDebInfo --target ext_patch cmake --build . --config RelWithDebInfo --target ext_png2ico ``` On OSX and Windows: ``` cmake --build . --config RelWithDebInfo --target ext_gettext cmake --build . --config RelWithDebInfo --target ext_openssl ``` On all operating systems: ``` cmake --build . --config RelWithDebInfo --target ext_qt cmake --build . --config RelWithDebInfo --target ext_zlib cmake --build . --config RelWithDebInfo --target ext_boost Note about boost: check if the headers are installed into i/include/boost, but not into i/include/boost-1.61/boost cmake --build . --config RelWithDebInfo --target ext_eigen3 cmake --build . --config RelWithDebInfo --target ext_exiv2 cmake --build . --config RelWithDebInfo --target ext_fftw3 cmake --build . --config RelWithDebInfo --target ext_ilmbase cmake --build . --config RelWithDebInfo --target ext_jpeg cmake --build . --config RelWithDebInfo --target ext_lcms2 cmake --build . --config RelWithDebInfo --target ext_ocio cmake --build . --config RelWithDebInfo --target ext_openexr ``` OSX Note: You need to first build openexr; that will fail; then you need to set the rpath for the two utilities correctly, then try to build openexr again. ``` install_name_tool -add_rpath $BUILD_ROOT/i/lib $BUILD_ROOT/b/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./b44ExpLogTable install_name_tool -add_rpath $BUILD_ROOT/i/lib $BUILD_ROOT/b/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./dwaLookups ``` On All operating systems ``` cmake --build . --config RelWithDebInfo --target ext_png cmake --build . --config RelWithDebInfo --target ext_tiff cmake --build . --config RelWithDebInfo --target ext_gsl cmake --build . --config RelWithDebInfo --target ext_vc cmake --build . --config RelWithDebInfo --target ext_libraw cmake --build . --config RelWithDebInfo --target ext_giflib cmake --build . --config RelWithDebInfo --target ext_openjpeg + 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 ``` 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_eigen3/CMakeLists.txt b/3rdparty/ext_eigen3/CMakeLists.txt index f32af14560..2c2d16f346 100644 --- a/3rdparty/ext_eigen3/CMakeLists.txt +++ b/3rdparty/ext_eigen3/CMakeLists.txt @@ -1,14 +1,14 @@ SET(EXTPREFIX_eigen3 "${EXTPREFIX}" ) ExternalProject_Add( ext_eigen3 DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} # eigen 3.3.4: bitbucket does weird things when downloading. - URL http://files.kde.org/krita/build/dependencies/eigen-eigen-5a0156e40feb.tar.gz - URL_MD5 1a47e78efe365a97de0c022d127607c3 + URL http://files.kde.org/krita/build/dependencies/eigen-3.3.7.tar.bz2 + URL_MD5 05b1f7511c93980c385ebe11bd3c93fa INSTALL_DIR ${EXTPREFIX_eigen3} PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/dart.diff COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/no_tests.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_eigen3} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} UPDATE_COMMAND "" ) diff --git a/3rdparty/ext_eigen3/dart.diff b/3rdparty/ext_eigen3/dart.diff index 15770fbe18..4df1bdf519 100644 --- a/3rdparty/ext_eigen3/dart.diff +++ b/3rdparty/ext_eigen3/dart.diff @@ -1,24 +1,24 @@ diff --git a/cmake/EigenConfigureTesting.cmake b/cmake/EigenConfigureTesting.cmake -index 2b11d83..8cf56ff 100644 +index 3a82439..4c28246 100644 --- a/cmake/EigenConfigureTesting.cmake +++ b/cmake/EigenConfigureTesting.cmake -@@ -26,19 +26,6 @@ include(CTest) - - set(EIGEN_TEST_BUILD_FLAGS " " CACHE STRING "Options passed to the build command of unit tests") +@@ -21,19 +21,6 @@ set(EIGEN_TEST_BUILD_FLAGS "" CACHE STRING "Options passed to the build command + set(EIGEN_DASHBOARD_BUILD_TARGET "buildtests" CACHE STRING "Target to be built in dashboard mode, default is buildtests") + set(EIGEN_CTEST_ERROR_EXCEPTION "" CACHE STRING "Regular expression for build error messages to be filtered out") -# Overwrite default DartConfiguration.tcl such that ctest can build our unit tests. -# Recall that our unit tests are not in the "all" target, so we have to explicitely ask ctest to build our custom 'buildtests' target. -# At this stage, we can also add custom flags to the build tool through the user defined EIGEN_TEST_BUILD_FLAGS variable. -file(READ "${CMAKE_CURRENT_BINARY_DIR}/DartConfiguration.tcl" EIGEN_DART_CONFIG_FILE) -# try to grab the default flags -string(REGEX MATCH "MakeCommand:.*-- (.*)\nDefaultCTestConfigurationType" EIGEN_DUMMY ${EIGEN_DART_CONFIG_FILE}) -if(NOT CMAKE_MATCH_1) -string(REGEX MATCH "MakeCommand:.*[^c]make (.*)\nDefaultCTestConfigurationType" EIGEN_DUMMY ${EIGEN_DART_CONFIG_FILE}) -endif() --string(REGEX REPLACE "MakeCommand:.*DefaultCTestConfigurationType" "MakeCommand: ${CMAKE_COMMAND} --build . --target buildtests --config \"\${CTEST_CONFIGURATION_TYPE}\" -- ${CMAKE_MATCH_1} ${EIGEN_TEST_BUILD_FLAGS}\nDefaultCTestConfigurationType" +-string(REGEX REPLACE "MakeCommand:.*DefaultCTestConfigurationType" "MakeCommand: ${CMAKE_COMMAND} --build . --target ${EIGEN_DASHBOARD_BUILD_TARGET} --config \"\${CTEST_CONFIGURATION_TYPE}\" -- ${CMAKE_MATCH_1} ${EIGEN_TEST_BUILD_FLAGS}\nDefaultCTestConfigurationType" - EIGEN_DART_CONFIG_FILE2 ${EIGEN_DART_CONFIG_FILE}) -file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/DartConfiguration.tcl" ${EIGEN_DART_CONFIG_FILE2}) - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/CTestCustom.cmake.in ${CMAKE_BINARY_DIR}/CTestCustom.cmake) # some documentation of this function would be nice diff --git a/3rdparty/ext_eigen3/no_tests.diff b/3rdparty/ext_eigen3/no_tests.diff index d943dab6d5..d20f5a0110 100644 --- a/3rdparty/ext_eigen3/no_tests.diff +++ b/3rdparty/ext_eigen3/no_tests.diff @@ -1,35 +1,13 @@ -commit 09b46c2a9acb0b7cb52968599499cb19fa5d8904 -Author: Boudewijn Rempt -Date: Fri Jan 5 16:09:39 2018 +0100 - - Disable tests: they need blas - diff --git a/CMakeLists.txt b/CMakeLists.txt -index f584002..dcb461b 100644 +index 2bfb6d5..81bffbe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt -@@ -418,24 +418,6 @@ add_subdirectory(doc EXCLUDE_FROM_ALL) +@@ -435,7 +435,7 @@ add_subdirectory(Eigen) - include(EigenConfigureTesting) + add_subdirectory(doc EXCLUDE_FROM_ALL) + +-option(BUILD_TESTING "Enable creation of Eigen tests." ON) ++option(BUILD_TESTING "Enable creation of Eigen tests." OFF) + if(BUILD_TESTING) + include(EigenConfigureTesting) --# fixme, not sure this line is still needed: --enable_testing() # must be called from the root CMakeLists, see man page -- -- --if(EIGEN_LEAVE_TEST_IN_ALL_TARGET) -- add_subdirectory(test) # can't do EXCLUDE_FROM_ALL here, breaks CTest --else() -- add_subdirectory(test EXCLUDE_FROM_ALL) --endif() -- --if(EIGEN_LEAVE_TEST_IN_ALL_TARGET) -- add_subdirectory(blas) -- add_subdirectory(lapack) --else() -- add_subdirectory(blas EXCLUDE_FROM_ALL) -- add_subdirectory(lapack EXCLUDE_FROM_ALL) --endif() -- - # add SYCL - option(EIGEN_TEST_SYCL "Add Sycl support." OFF) - if(EIGEN_TEST_SYCL) diff --git a/3rdparty/ext_qt/CMakeLists.txt b/3rdparty/ext_qt/CMakeLists.txt index 83e613f9e3..f92f91088b 100644 --- a/3rdparty/ext_qt/CMakeLists.txt +++ b/3rdparty/ext_qt/CMakeLists.txt @@ -1,309 +1,310 @@ SET(EXTPREFIX_qt "${EXTPREFIX}") if (WIN32) list(APPEND _QT_conf -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtdoc -skip qtgraphicaleffects -skip qtlocation -skip qtsensors -skip qtserialport -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtxmlpatterns -no-sql-sqlite -nomake examples -nomake tools -no-compile-examples -no-dbus -no-iconv -no-qml-debug -no-libproxy -no-system-proxies -no-icu -no-mtdev -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtnetworkauth -skip qtpurchasing -skip qtremoteobjects -skip qtscxml -skip qtserialbus -skip qtspeech -skip qtvirtualkeyboard # -qt-zlib -qt-pcre -qt-libpng -qt-libjpeg -openssl-linked -I ${EXTPREFIX_qt}/include -L ${EXTPREFIX_qt}/lib # -opensource -confirm-license # -release -platform win32-g++ -prefix ${EXTPREFIX_qt} QMAKE_LFLAGS_APP+=${SECURITY_EXE_LINKER_FLAGS} QMAKE_LFLAGS_SHLIB+=${SECURITY_SHARED_LINKER_FLAGS} QMAKE_LFLAGS_SONAME+=${SECURITY_SHARED_LINKER_FLAGS} ) if (QT_ENABLE_DEBUG_INFO) # Set the option to build Qt with debugging info enabled list(APPEND _QT_conf -force-debug-info) endif(QT_ENABLE_DEBUG_INFO) if (QT_ENABLE_DYNAMIC_OPENGL) list(APPEND _QT_conf -opengl dynamic -angle) else (QT_ENABLE_DYNAMIC_OPENGL) list(APPEND _QT_conf -opengl desktop -no-angle) endif (QT_ENABLE_DYNAMIC_OPENGL) # MIME-type optimization patches set(ext_qt_PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0001-Use-fast-path-for-unsupported-mime-types.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0002-Hack-always-return-we-support-DIBV5.patch ) # Tablet support patches if (NOT USE_QT_TABLET_WINDOWS) set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND} COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0001-disable-wintab.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/disable-winink.patch ) else() set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND} COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0020-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0023-Implement-a-switch-for-tablet-API-on-Windows.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0024-Fetch-stylus-button-remapping-from-WinTab-driver.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0025-Disable-tablet-relative-mode-in-Qt.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0026-Fetch-mapped-screen-size-from-the-Wintab-driver.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0027-Switch-stylus-pointer-type-when-the-tablet-is-in-the.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0028-Fix-updating-tablet-pressure-resolution-on-every-pro.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0051-Add-workaround-for-handling-table-press-correctly-in.patch ) endif() # HDR patches set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND} COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0003-Implement-openGL-surface-color-space-selection-in-An.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0004-Implement-color-space-selection-for-QSurfaceFormat.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0005-Implement-color-conversion-for-the-backing-store-tex.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0006-Return-QScreen-s-HMONITOR-handle-via-QPlatformNative.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0007-Implement-a-manual-test-for-checking-is-HDR-features.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0008-Fix-notification-of-QDockWidget-when-it-gets-undocke.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0009-Fix-Rec2020-display-format.patch ) # AMD random generation patch set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND} COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0070-Fix-QRandomGenerator-initialization-on-AMD-CPUs.patch ) # Other patches set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND} COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0060-Windows-Add-a-default-setting-for-hasBorderInFullScr.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0061-Hack-to-hide-1px-border-with-OpenGL-fullscreen-hack.patch COMMAND ${PATCH_COMMAND} -p1 -d qttools -i ${CMAKE_CURRENT_SOURCE_DIR}/windeployqt-force-allow-debug-info.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0080-Sync-buffers-of-the-destination-file-after-QFile-cop.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0082-jpeg-extensions-handling-in-non-native-file-dialogs.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0031-Compute-logical-DPI-on-a-per-screen-basis.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0032-Update-Dpi-and-scale-factor-computation.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0033-Move-QT_FONT_DPI-to-cross-platform-code.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0034-Update-QT_SCREEN_SCALE_FACTORS.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0035-Deprecate-QT_AUTO_SCREEN_SCALE_FACTOR.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0036-Add-high-DPI-scale-factor-rounding-policy-C-API.patch ) ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.qt.io/official_releases/qt/5.12/5.12.5/single/qt-everywhere-src-5.12.5.zip URL_MD5 157fa5d9c897737ce06ba4f9a20151e0 PATCH_COMMAND ${ext_qt_PATCH_COMMAND} INSTALL_DIR ${EXTPREFIX_qt} CONFIGURE_COMMAND /configure.bat ${_QT_conf} BUILD_COMMAND mingw32-make -j${SUBMAKE_JOBS} INSTALL_COMMAND mingw32-make -j${SUBMAKE_JOBS} install UPDATE_COMMAND "" # Use a short name to reduce the chance of exceeding path length limit SOURCE_DIR s BINARY_DIR b DEPENDS ext_patch ext_openssl ) elseif (ANDROID) ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.qt.io/archive/qt/5.12/5.12.2/single/qt-everywhere-src-5.12.2.tar.xz URL_MD5 99c2eb46e533371798b4ca2d1458e065 PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/show-proper-error.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/workaround-null-eglconfig-crash.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/fix-android-menu64bit.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/android-add-pen-tilt-rot.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0082-jpeg-extensions-handling-in-non-native-file-dialogs.patch CONFIGURE_COMMAND /configure -prefix ${EXTPREFIX_qt} -opensource -confirm-license -verbose -nomake examples -nomake tests -nomake tools -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtgraphicaleffects -skip qtlocation -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtserialport -skip qtdatavis3d -skip qtvirtualkeyboard -skip qtspeech -skip qtsensors -skip qtgamepad -skip qtscxml -skip qtremoteobjects -skip qtxmlpatterns -skip qtnetworkauth -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtpurchasing -skip qtscxml -skip qtserialbus -skip qtspeech -skip qtvirtualkeyboard -skip qtmultimedia -android-sdk ${ANDROID_SDK_ROOT} -android-ndk ${CMAKE_ANDROID_NDK} -android-arch ${ANDROID_ABI} -xplatform android-clang -android-ndk-platform android-21 -make libs INSTALL_DIR ${EXTPREFIX_qt} BUILD_COMMAND $(MAKE) INSTALL_COMMAND $(MAKE) install UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) elseif (NOT APPLE) ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.qt.io/official_releases/qt/5.12/5.12.5/single/qt-everywhere-src-5.12.5.tar.xz URL_MD5 d8c9ed842d39f1a5f31a7ab31e4e886c PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0013-Poison-Qt-s-headers-with-a-mark-about-presence-of-En.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0070-Fix-QRandomGenerator-initialization-on-AMD-CPUs.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0082-jpeg-extensions-handling-in-non-native-file-dialogs.patch CMAKE_ARGS -DOPENSSL_LIBS='-L${EXTPREFIX_qt}/lib -lssl -lcrypto' CONFIGURE_COMMAND /configure -prefix ${EXTPREFIX_qt} -opensource -confirm-license -openssl-linked -verbose -nomake examples -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtgraphicaleffects -skip qtlocation -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtandroidextras -skip qtserialport -skip qtdatavis3d -skip qtvirtualkeyboard -skip qtspeech -skip qtsensors -skip qtgamepad -skip qtscxml -skip qtremoteobjects -skip qtxmlpatterns -skip qtnetworkauth -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtpurchasing -skip qtscxml -skip qtserialbus -skip qtspeech -skip qtvirtualkeyboard -skip qtmultimedia INSTALL_DIR ${EXTPREFIX_qt} BUILD_COMMAND $(MAKE) INSTALL_COMMAND $(MAKE) install UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) else( APPLE ) # XCODE_VERSION is set by CMake when using the Xcode generator, otherwise we need # to detect it manually here. if (NOT XCODE_VERSION) execute_process( COMMAND xcodebuild -version OUTPUT_VARIABLE xcodebuild_version OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_FILE /dev/null ) string(REGEX MATCH "Xcode ([0-9]+([.][0-9]+)*)" version_match ${xcodebuild_version}) if (version_match) message(STATUS "${EXTPREFIX_qt}:Identified Xcode Version: ${CMAKE_MATCH_1}") set(XCODE_VERSION ${CMAKE_MATCH_1}) else() # If detecting Xcode version failed, set a crazy high version so we default # to the newest. set(XCODE_VERSION 99) message(WARNING "${EXTPREFIX_qt}:Failed to detect the version of an installed copy of Xcode, falling back to highest supported version. Set XCODE_VERSION to override.") endif(version_match) endif(NOT XCODE_VERSION) # ------------------------------------------------------------------------------- # Verify the Xcode installation on Mac OS like Qt5.7 does/will # If not stop now, the system isn't configured correctly for Qt. # No reason to even proceed. # ------------------------------------------------------------------------------- set(XCSELECT_OUTPUT) find_program(XCSELECT_PROGRAM "xcode-select") if(XCSELECT_PROGRAM) message(STATUS "${EXTPREFIX_qt}:Found XCSELECT_PROGRAM as ${XCSELECT_PROGRAM}") set(XCSELECT_COMMAND ${XCSELECT_PROGRAM} "--print-path") execute_process( COMMAND ${XCSELECT_COMMAND} RESULT_VARIABLE XCSELECT_COMMAND_RESULT OUTPUT_VARIABLE XCSELECT_COMMAND_OUTPUT ERROR_FILE /dev/null ) if(NOT XCSELECT_COMMAND_RESULT) # returned 0, we're ok. string(REGEX REPLACE "[ \t]*[\r\n]+[ \t]*" ";" XCSELECT_COMMAND_OUTPUT ${XCSELECT_COMMAND_OUTPUT}) else() string(REPLACE ";" " " XCSELECT_COMMAND_STR "${XCSELECT_COMMAND}") # message(STATUS "${XCSELECT_COMMAND_STR}") message(FATAL_ERROR "${EXTPREFIX_qt}:${XCSELECT_PROGRAM} test failed with status ${XCSELECT_COMMAND_RESULT}") endif() else() message(FATAL_ERROR "${EXTPREFIX_qt}:${XCSELECT_PROGRAM} not found. No Xcode is selected. Use xcode-select -switch to choose an Xcode version") endif() # Belts and suspenders # Beyond all the Xcode and Qt version checking, the proof of the pudding # lies in the success/failure of this command: xcrun --find xcrun. # On failure a patch is necessary, otherwise we're ok # So hard check xcrun now... set(XCRUN_OUTPUT) find_program(XCRUN_PROGRAM "xcrun") if(XCRUN_PROGRAM) message(STATUS "${EXTPREFIX_qt}:Found XCRUN_PROGRAM as ${XCRUN_PROGRAM}") set(XCRUN_COMMAND ${XCRUN_PROGRAM} "--find xcrun") execute_process( COMMAND ${XCRUN_COMMAND} RESULT_VARIABLE XCRUN_COMMAND_RESULT OUTPUT_VARIABLE XCRUN_COMMAND_OUTPUT ERROR_FILE /dev/null ) if(NOT XCRUN_COMMAND_RESULT) # returned 0, we're ok. string(REGEX REPLACE "[ \t]*[\r\n]+[ \t]*" ";" XCRUN_COMMAND_OUTPUT ${XCRUN_COMMAND_OUTPUT}) else() string(REPLACE ";" " " XCRUN_COMMAND_STR "${XCRUN_COMMAND}") # message(STATUS "${XCRUN_COMMAND_STR}") message(STATUS "${EXTPREFIX_qt}:xcrun test failed with status ${XCRUN_COMMAND_RESULT}") endif() else() message(STATUS "${EXTPREFIX_qt}:xcrun not found -- ${XCRUN_PROGRAM}") endif() # # Now configure ext_qt accordingly # if ((XCRUN_COMMAND_RESULT) AND (NOT (XCODE_VERSION VERSION_LESS 8.0.0))) # Fix Xcode xcrun related issue. # NOTE: This should be fixed by Qt 5.7.1 see here: http://code.qt.io/cgit/qt/qtbase.git/commit/?h=dev&id=77a71c32c9d19b87f79b208929e71282e8d8b5d9 # NOTE: but no one's holding their breath. set(ext_qt_PATCH_COMMAND ${PATCH_COMMAND}} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0013-Poison-Qt-s-headers-with-a-mark-about-presence-of-En.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0070-Fix-QRandomGenerator-initialization-on-AMD-CPUs.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0082-jpeg-extensions-handling-in-non-native-file-dialogs.patch #COMMAND ${PATCH_COMMAND} -p1 -b -d /qtbase/mkspecs/features/mac -i ${CMAKE_CURRENT_SOURCE_DIR}/mac-default.patch ) message(STATUS "${EXTPREFIX_qt}:Additional patches injected.") else() # No extra patches will be applied # NOTE: defaults for some untested scenarios like xcrun fails and xcode_version < 8. # NOTE: that is uncharted territory and (hopefully) a very unlikely scenario... set(ext_qt_PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0082-jpeg-extensions-handling-in-non-native-file-dialogs.patch ) endif() # Qt is big - try and parallelize if at all possible include(ProcessorCount) ProcessorCount(NUM_CORES) if(NOT NUM_CORES EQUAL 0) if (NUM_CORES GREATER 2) # be nice... MATH( EXPR NUM_CORES "${NUM_CORES} - 2" ) endif() set(PARALLEL_MAKE "make;-j${NUM_CORES}") message(STATUS "${EXTPREFIX_qt}:Parallelized make: ${PARALLEL_MAKE}") else() set(PARALLEL_MAKE "make") endif() ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} LOG_DOWNLOAD ON LOG_UPDATE ON LOG_CONFIGURE ON LOG_BUILD ON LOG_TEST ON LOG_INSTALL ON BUILD_IN_SOURCE ON URL https://download.qt.io/official_releases/qt/5.12/5.12.5/single/qt-everywhere-src-5.12.5.tar.xz URL_MD5 d8c9ed842d39f1a5f31a7ab31e4e886c CMAKE_ARGS -DOPENSSL_LIBS='-L${EXTPREFIX_qt}/lib -lssl -lcrypto' INSTALL_DIR ${EXTPREFIX_qt} CONFIGURE_COMMAND /configure -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtdoc -skip qtgraphicaleffects -skip qtlocation -skip qtsensors -skip qtserialport -skip qtwayland -skip qtwebchannel -skip qtwebsockets -skip qtwebview -skip qtwebengine -skip qtxmlpatterns -no-sql-sqlite -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtnetworkauth -skip qtpurchasing -skip qtremoteobjects -skip qtscxml -skip qtserialbus -skip qtspeech -skip qtvirtualkeyboard -nomake examples -nomake tools -no-compile-examples -no-dbus -no-iconv -no-qml-debug -no-libproxy -no-system-proxies -no-icu -no-mtdev -system-zlib -qt-pcre -opensource -confirm-license -openssl-linked -prefix ${EXTPREFIX_qt} BUILD_COMMAND ${PARALLEL_MAKE} INSTALL_COMMAND make install UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) endif() diff --git a/3rdparty/ext_qt/android-add-pen-tilt-rot.patch b/3rdparty/ext_qt/android-add-pen-tilt-rot.patch new file mode 100644 index 0000000000..5457d46526 --- /dev/null +++ b/3rdparty/ext_qt/android-add-pen-tilt-rot.patch @@ -0,0 +1,67 @@ +From 35ae5101395b96c5505cdaa76f9f2ce9da089cfe Mon Sep 17 00:00:00 2001 +From: Max Thomas +Date: Wed, 08 Jan 2020 10:34:10 -0700 +Subject: [PATCH] Add support for pen tilt/rotation for Android + +Change-Id: I195c891b47841ac86048dc38ea95beaeced8b70a +--- + +diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java +index dee5628..0862383 100644 +--- a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java ++++ b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java +@@ -515,8 +515,13 @@ + if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) { + sendMouseEvent(event, id); + } else if (m_tabletEventSupported && pointerType != 0) { ++ float tiltRot = event.getAxisValue(MotionEvent.AXIS_TILT); ++ float orientation = event.getAxisValue(MotionEvent.AXIS_ORIENTATION); ++ float tiltX = (float) Math.toDegrees(-Math.sin(orientation) * tiltRot); ++ float tiltY = (float) Math.toDegrees(Math.cos(orientation) * tiltRot); + tabletEvent(id, event.getDeviceId(), event.getEventTime(), event.getAction(), pointerType, +- event.getButtonState(), event.getX(), event.getY(), event.getPressure()); ++ event.getButtonState(), event.getX(), event.getY(), event.getPressure(), tiltX, tiltY, ++ (float) Math.toDegrees(orientation)); + } else { + touchBegin(id); + for (int i = 0; i < event.getPointerCount(); ++i) { +@@ -1069,7 +1074,7 @@ + + // tablet methods + public static native boolean isTabletEventSupported(); +- public static native void tabletEvent(int winId, int deviceId, long time, int action, int pointerType, int buttonState, float x, float y, float pressure); ++ public static native void tabletEvent(int winId, int deviceId, long time, int action, int pointerType, int buttonState, float x, float y, float pressure, float tiltX, float tiltY, float rotation); + // tablet methods + + // keyboard methods +diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp +index 049f9b0..9768e52 100644 +--- a/src/plugins/platforms/android/androidjniinput.cpp ++++ b/src/plugins/platforms/android/androidjniinput.cpp +@@ -304,7 +304,7 @@ + } + + static void tabletEvent(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint deviceId, jlong time, jint action, +- jint pointerType, jint buttonState, jfloat x, jfloat y, jfloat pressure) ++ jint pointerType, jint buttonState, jfloat x, jfloat y, jfloat pressure, jfloat tiltX, jfloat tiltY, jfloat rotation) + { + #if QT_CONFIG(tabletevent) + QPointF globalPosF(x, y); +@@ -347,7 +347,7 @@ + + QWindowSystemInterface::handleTabletEvent(tlw, ulong(time), + localPos, globalPosF, QTabletEvent::Stylus, pointerType, +- buttons, pressure, 0, 0, 0., 0., 0, deviceId, Qt::NoModifier); ++ buttons, pressure, tiltX, tiltY, 0., rotation, 0, deviceId, Qt::NoModifier); + #endif // QT_CONFIG(tabletevent) + } + +@@ -852,7 +852,7 @@ + {"mouseWheel", "(IIIFF)V", (void *)mouseWheel}, + {"longPress", "(III)V", (void *)longPress}, + {"isTabletEventSupported", "()Z", (void *)isTabletEventSupported}, +- {"tabletEvent", "(IIJIIIFFF)V", (void *)tabletEvent}, ++ {"tabletEvent", "(IIJIIIFFFFFF)V", (void *)tabletEvent}, + {"keyDown", "(IIIZ)V", (void *)keyDown}, + {"keyUp", "(IIIZ)V", (void *)keyUp}, + {"keyboardVisibilityChanged", "(Z)V", (void *)keyboardVisibilityChanged}, diff --git a/CMakeLists.txt b/CMakeLists.txt index cd95efc2cd..f7d07d2a52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,926 +1,925 @@ project(krita) message(STATUS "Using CMake version: ${CMAKE_VERSION}") cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR) set(MIN_QT_VERSION 5.9.0) set(MIN_FRAMEWORKS_VERSION 5.44.0) set( CMAKE_CXX_STANDARD 11 ) set( CMAKE_CXX_STANDARD_REQUIRED ON ) if (POLICY CMP0002) cmake_policy(SET CMP0002 OLD) endif() if (POLICY CMP0017) cmake_policy(SET CMP0017 NEW) endif () if (POLICY CMP0022) cmake_policy(SET CMP0022 OLD) endif () if (POLICY CMP0026) cmake_policy(SET CMP0026 OLD) endif() if (POLICY CMP0042) cmake_policy(SET CMP0042 NEW) endif() if (POLICY CMP0046) cmake_policy(SET CMP0046 OLD) endif () if (POLICY CMP0059) cmake_policy(SET CMP0059 OLD) endif() if (POLICY CMP0063) cmake_policy(SET CMP0063 OLD) endif() if (POLICY CMP0054) cmake_policy(SET CMP0054 OLD) endif() if (POLICY CMP0064) cmake_policy(SET CMP0064 OLD) endif() if (POLICY CMP0071) cmake_policy(SET CMP0071 OLD) endif() if (APPLE) set(APPLE_SUPPRESS_X11_WARNING TRUE) set(KDE_SKIP_RPATH_SETTINGS TRUE) set(CMAKE_MACOSX_RPATH 1) set(BUILD_WITH_INSTALL_RPATH 1) add_definitions(-mmacosx-version-min=10.12 -Wno-macro-redefined -Wno-deprecated-register) endif() if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9 AND NOT WIN32) add_compile_options($<$:-Wno-suggest-override> -Wextra -Wno-class-memaccess) endif() ###################### ####################### ## Constants defines ## ####################### ###################### # define common versions of Krita applications, used to generate kritaversion.h # update these version for every release: set(KRITA_VERSION_STRING "4.3.0-prealpha") # Major version: 3 for 3.x, 4 for 4.x, etc. set(KRITA_STABLE_VERSION_MAJOR 4) # Minor version: 0 for 4.0, 1 for 4.1, etc. set(KRITA_STABLE_VERSION_MINOR 3) # Bugfix release version, or 0 for before the first stable release set(KRITA_VERSION_RELEASE 0) # the 4th digit, really only used for the Windows installer: # - [Pre-]Alpha: Starts from 0, increment 1 per release # - Beta: Starts from 50, increment 1 per release # - Stable: Set to 100, bump to 101 if emergency update is needed set(KRITA_VERSION_REVISION 0) set(KRITA_ALPHA 1) # uncomment only for Alpha #set(KRITA_BETA 1) # uncomment only for Beta #set(KRITA_RC 1) # uncomment only for RC -set(KRITA_YEAR 2018) # update every year if(NOT DEFINED KRITA_ALPHA AND NOT DEFINED KRITA_BETA AND NOT DEFINED KRITA_RC) set(KRITA_STABLE 1) # do not edit endif() message(STATUS "Krita version: ${KRITA_VERSION_STRING}") # Define the generic version of the Krita libraries here # This makes it easy to advance it when the next Krita release comes. # 14 was the last GENERIC_KRITA_LIB_VERSION_MAJOR of the previous Krita series # (2.x) so we're starting with 15 in 3.x series, 16 in 4.x series if(KRITA_STABLE_VERSION_MAJOR EQUAL 4) math(EXPR GENERIC_KRITA_LIB_VERSION_MAJOR "${KRITA_STABLE_VERSION_MINOR} + 16") else() # let's make sure we won't forget to update the "16" message(FATAL_ERROR "Reminder: please update offset == 16 used to compute GENERIC_KRITA_LIB_VERSION_MAJOR to something bigger") endif() set(GENERIC_KRITA_LIB_VERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}.0.0") set(GENERIC_KRITA_LIB_SOVERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/kde_macro") # fetch git revision for the current build set(KRITA_GIT_SHA1_STRING "") set(KRITA_GIT_BRANCH_STRING "") include(GetGitRevisionDescription) get_git_head_hash(GIT_SHA1) get_git_branch(GIT_BRANCH) if(GIT_SHA1) string(SUBSTRING ${GIT_SHA1} 0 7 GIT_SHA1) set(KRITA_GIT_SHA1_STRING ${GIT_SHA1}) if(GIT_BRANCH) set(KRITA_GIT_BRANCH_STRING ${GIT_BRANCH}) else() set(KRITA_GIT_BRANCH_STRING "(detached HEAD)") endif() endif() # create test make targets enable_testing() # collect list of broken tests, empty here to start fresh with each cmake run set(KRITA_BROKEN_TESTS "" CACHE INTERNAL "KRITA_BROKEN_TESTS") ############ ############# ## Options ## ############# ############ include(FeatureSummary) if (WIN32) option(USE_DRMINGW "Support the Dr. Mingw crash handler (only on windows)" ON) add_feature_info("Dr. Mingw" USE_DRMINGW "Enable the Dr. Mingw crash handler") if (MINGW) option(USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags (mingw-w64)" ON) add_feature_info("Linker Security Flags" USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags") if (USE_MINGW_HARDENING_LINKER) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") # Enable high-entropy ASLR for 64-bit # The image base has to be >4GB for HEASLR to be enabled. # The values used here are kind of arbitrary. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x140000000") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") endif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") else (USE_MINGW_HARDENING_LINKER) message(WARNING "Linker Security Flags not enabled!") endif (USE_MINGW_HARDENING_LINKER) endif (MINGW) endif () option(HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal." ON) configure_file(config-hide-safe-asserts.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hide-safe-asserts.h) add_feature_info("Hide Safe Asserts" HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal.") option(USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking." ON) configure_file(config-hash-table-implementaion.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hash-table-implementaion.h) add_feature_info("Lock free hash table" USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking.") option(FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true." OFF) add_feature_info("Foundation Build" FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true.") option(KRITA_ENABLE_BROKEN_TESTS "Enable tests that are marked as broken" OFF) add_feature_info("Enable Broken Tests" KRITA_ENABLE_BROKEN_TESTS "Runs broken test when \"make test\" is invoked (use -DKRITA_ENABLE_BROKEN_TESTS=ON to enable).") option(LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode" ON) configure_file(config-limit-long-tests.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-limit-long-tests.h) add_feature_info("Limit long tests" LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode") option(ENABLE_PYTHON_2 "Enables the compiler to look for Python 2.7 instead of Python 3. Some packaged scripts are not compatible with Python 2 and this should only be used if you really have to use 2.7." OFF) option(BUILD_KRITA_QT_DESIGNER_PLUGINS "Build Qt Designer plugins for Krita widgets" OFF) add_feature_info("Build Qt Designer plugins" BUILD_KRITA_QT_DESIGNER_PLUGINS "Builds Qt Designer plugins for Krita widgets (use -DBUILD_KRITA_QT_DESIGNER_PLUGINS=ON to enable).") include(MacroJPEG) ######################################################### ## Look for Python3 It is also searched by KF5, ## ## so we should request the correct version in advance ## ######################################################### function(TestCompileLinkPythonLibs OUTPUT_VARNAME) include(CheckCXXSourceCompiles) set(CMAKE_REQUIRED_INCLUDES ${PYTHON_INCLUDE_PATH}) set(CMAKE_REQUIRED_LIBRARIES ${PYTHON_LIBRARIES}) if (MINGW) set(CMAKE_REQUIRED_DEFINITIONS -D_hypot=hypot) endif (MINGW) unset(${OUTPUT_VARNAME} CACHE) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { Py_InitializeEx(0); }" ${OUTPUT_VARNAME}) endfunction() if(MINGW) if(ENABLE_PYTHON_2) message(FATAL_ERROR "Python 2.7 is not supported on Windows at the moment.") else(ENABLE_PYTHON_2) find_package(PythonInterp 3.6 EXACT) find_package(PythonLibs 3.6 EXACT) endif(ENABLE_PYTHON_2) if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) if(ENABLE_PYTHON_2) find_package(PythonLibrary 2.7) else(ENABLE_PYTHON_2) find_package(PythonLibrary 3.6) endif(ENABLE_PYTHON_2) TestCompileLinkPythonLibs(CAN_USE_PYTHON_LIBS) if (NOT CAN_USE_PYTHON_LIBS) message(FATAL_ERROR "Compiling with Python library failed, please check whether the architecture is correct. Python will be disabled.") endif (NOT CAN_USE_PYTHON_LIBS) endif (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) else(MINGW) if(ENABLE_PYTHON_2) find_package(PythonInterp 2.7) find_package(PythonLibrary 2.7) else(ENABLE_PYTHON_2) find_package(PythonInterp 3.0) find_package(PythonLibrary 3.0) endif(ENABLE_PYTHON_2) endif(MINGW) ######################## ######################### ## Look for KDE and Qt ## ######################### ######################## find_package(ECM 5.22 REQUIRED NOMODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(ECMOptionalAddSubdirectory) include(ECMAddAppIcon) include(ECMSetupVersion) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(GenerateExportHeader) include(ECMMarkAsTest) include(ECMInstallIcons) include(CMakePackageConfigHelpers) include(WriteBasicConfigVersionFile) include(CheckFunctionExists) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings) # do not reorder to be alphabetical: this is the order in which the frameworks # depend on each other. find_package(KF5 ${MIN_FRAMEWORKS_VERSION} REQUIRED COMPONENTS Config WidgetsAddons Completion CoreAddons GuiAddons I18n ItemModels ItemViews WindowSystem Archive ) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS Core Gui Widgets Xml Network PrintSupport Svg Test Concurrent ) if (ANDROID) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS AndroidExtras ) endif() if (WIN32) set(CMAKE_REQUIRED_INCLUDES ${Qt5Core_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${Qt5Core_LIBRARIES}) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI); } " QT_HAS_WINTAB_SWITCH ) unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) option(USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt." ON) add_feature_info("Use Qt's Windows Tablet Support" USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt.") configure_file(config_use_qt_tablet_windows.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_use_qt_tablet_windows.h) endif () set(CMAKE_REQUIRED_INCLUDES ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${Qt5Core_LIBRARIES} ${Qt5Gui_LIBRARIES}) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QSurfaceFormat fmt; fmt.setColorSpace(QSurfaceFormat::scRGBColorSpace); fmt.setColorSpace(QSurfaceFormat::bt2020PQColorSpace); } " HAVE_HDR ) configure_file(config-hdr.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hdr.h) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Round); QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor); QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); } " HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY ) configure_file(config-high-dpi-scale-factor-rounding-policy.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-high-dpi-scale-factor-rounding-policy.h) if (WIN32) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true); } " HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT ) configure_file(config-set-has-border-in-full-screen-default.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-set-has-border-in-full-screen-default.h) endif (WIN32) unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) include (MacroAddFileDependencies) include (MacroBoolTo01) include (MacroEnsureOutOfSourceBuild) macro_ensure_out_of_source_build("Compiling Krita inside the source directory is not possible. Please refer to the build instruction https://community.kde.org/Krita#Build_Instructions") # Note: OPTIONAL_COMPONENTS does not seem to be reliable # (as of ECM 5.15.0, CMake 3.2) find_package(Qt5Multimedia ${MIN_QT_VERSION}) set_package_properties(Qt5Multimedia PROPERTIES DESCRIPTION "Qt multimedia integration" - URL "http://www.qt.io/" + URL "https://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide sound support for animations") macro_bool_to_01(Qt5Multimedia_FOUND HAVE_QT_MULTIMEDIA) configure_file(config-qtmultimedia.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-qtmultimedia.h ) if (NOT APPLE) find_package(Qt5Quick ${MIN_QT_VERSION}) set_package_properties(Qt5Quick PROPERTIES DESCRIPTION "QtQuick" - URL "http://www.qt.io/" + URL "https://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used for the touch gui for Krita") macro_bool_to_01(Qt5Quick_FOUND HAVE_QT_QUICK) find_package(Qt5QuickWidgets ${MIN_QT_VERSION}) set_package_properties(Qt5QuickWidgets PROPERTIES DESCRIPTION "QtQuickWidgets" - URL "http://www.qt.io/" + URL "https://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used for the touch gui for Krita") endif() if (NOT WIN32 AND NOT APPLE AND NOT ANDROID) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED X11Extras) find_package(Qt5DBus ${MIN_QT_VERSION}) set(HAVE_DBUS ${Qt5DBus_FOUND}) set_package_properties(Qt5DBus PROPERTIES DESCRIPTION "Qt DBUS integration" - URL "http://www.qt.io/" + URL "https://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide a dbus api on Linux") find_package(KF5Crash ${MIN_FRAMEWORKS_VERSION}) macro_bool_to_01(KF5Crash_FOUND HAVE_KCRASH) set_package_properties(KF5Crash PROPERTIES DESCRIPTION "KDE's Crash Handler" - URL "http://api.kde.org/frameworks-api/frameworks5-apidocs/kcrash/html/index.html" + URL "https://api.kde.org/frameworks-api/frameworks5-apidocs/kcrash/html/index.html" TYPE OPTIONAL PURPOSE "Optionally used to provide crash reporting on Linux") find_package(X11 REQUIRED COMPONENTS Xinput) set(HAVE_X11 TRUE) add_definitions(-DHAVE_X11) else() set(HAVE_DBUS FALSE) set(HAVE_X11 FALSE) endif() add_definitions( -DQT_USE_QSTRINGBUILDER -DQT_STRICT_ITERATORS -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING -DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS ) #if (${Qt5_VERSION} VERSION_GREATER "5.14.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50F00) #elseif (${Qt5_VERSION} VERSION_GREATER "5.13.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50E00) #elseif (${Qt5_VERSION} VERSION_GREATER "5.12.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50D00) #elseif (${Qt5_VERSION} VERSION_GREATER "5.11.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50C00) #if(${Qt5_VERSION} VERSION_GREATER "5.10.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50B00) #if(${Qt5_VERSION} VERSION_GREATER "5.9.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50A00) #else() add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50900) #endif() add_definitions(-DQT_DEPRECATED_WARNINGS) add_definitions(-DTRANSLATION_DOMAIN=\"krita\") # # The reason for this mode is that the Debug mode disable inlining # if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals") endif() option(KRITA_DEVS "For Krita developers. This modifies the DEBUG build type to use -O3 -g, while still enabling Q_ASSERT. This is necessary because the Qt5 cmake modules normally append QT_NO_DEBUG to any build type that is not labeled Debug") if (KRITA_DEVS) set(CMAKE_CXX_FLAGS_DEBUG "-O3 -g" CACHE STRING "" FORCE) endif() if(UNIX) set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES};m") endif() if(WIN32) if(MSVC) # C4522: 'class' : multiple assignment operators specified set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd4522") endif() endif() # KDECompilerSettings adds the `--export-all-symbols` linker flag. # We don't really need it. if(MINGW) string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}") string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}") endif(MINGW) if(MINGW) # Hack CMake's variables to tell AR to create thin archives to reduce unnecessary writes. # Source of definition: https://github.com/Kitware/CMake/blob/v3.14.1/Modules/Platform/Windows-GNU.cmake#L128 # Thin archives: https://sourceware.org/binutils/docs/binutils/ar.html#index-thin-archives macro(mingw_use_thin_archive lang) foreach(rule CREATE_SHARED_MODULE CREATE_SHARED_LIBRARY LINK_EXECUTABLE) string(REGEX REPLACE "( [^ T]+) " "\\1T " CMAKE_${lang}_${rule} "${CMAKE_${lang}_${rule}}") endforeach() endmacro() mingw_use_thin_archive(CXX) endif(MINGW) # enable exceptions globally kde_enable_exceptions() set(KRITA_DEFAULT_TEST_DATA_DIR ${CMAKE_SOURCE_DIR}/sdk/tests/data/) macro(macro_add_unittest_definitions) add_definitions(-DFILES_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data/") add_definitions(-DFILES_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}") add_definitions(-DFILES_DEFAULT_DATA_DIR="${KRITA_DEFAULT_TEST_DATA_DIR}") add_definitions(-DSYSTEM_RESOURCES_DATA_DIR="${CMAKE_SOURCE_DIR}/krita/data/") endmacro() # overcome some platform incompatibilities if(WIN32) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/winquirks) add_definitions(-D_USE_MATH_DEFINES) add_definitions(-DNOMINMAX) set(WIN32_PLATFORM_NET_LIBS ws2_32.lib netapi32.lib) endif() # set custom krita plugin installdir if (ANDROID) # use default ABI if (NOT ANDROID_ABI) set (ANDROID_ABI armeabi-v7a) endif() set (ANDROID_SDK_ROOT $ENV{ANDROID_SDK_ROOT}) set (KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}) # set (DATA_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/assets) else() set (KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}/kritaplugins) endif() ########################### ############################ ## Required dependencies ## ############################ ########################### # FIXME: Still hardcoded if (ANDROID) set (Boost_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/${ANDROID_ABI}/include/boost-1_69) set (Boost_LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/${ANDROID_ABI}/lib) set (KF5_LIBRARIES ${CMAKE_CURRENT_BINARY_DIR}/kf5/kde/install/lib) endif() find_package(PNG REQUIRED) list (APPEND ANDROID_EXTRA_LIBS ${PNG_LIBRARY}) if (APPLE) - # this is not added correctly on OSX -- see http://forum.kde.org/viewtopic.php?f=139&t=101867&p=221242#p221242 + # this is not added correctly on OSX -- see https://forum.kde.org/viewtopic.php?f=139&t=101867&p=221242#p221242 include_directories(SYSTEM ${PNG_INCLUDE_DIR}) endif() add_definitions(-DBOOST_ALL_NO_LIB) find_package(Boost 1.55 REQUIRED COMPONENTS system) include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) ## ## Test for GNU Scientific Library ## find_package(GSL) set_package_properties(GSL PROPERTIES - URL "http://www.gnu.org/software/gsl" + URL "https://www.gnu.org/software/gsl" TYPE RECOMMENDED PURPOSE "Required by Krita's Transform tool.") macro_bool_to_01(GSL_FOUND HAVE_GSL) configure_file(config-gsl.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-gsl.h ) if (GSL_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) endif() ########################### ############################ ## Optional dependencies ## ############################ ########################### find_package(ZLIB) set_package_properties(ZLIB PROPERTIES DESCRIPTION "Compression library" - URL "http://www.zlib.net/" + URL "https://www.zlib.net/" TYPE OPTIONAL PURPOSE "Optionally used by the G'Mic and the PSD plugins") macro_bool_to_01(ZLIB_FOUND HAVE_ZLIB) find_package(OpenEXR) set_package_properties(OpenEXR PROPERTIES DESCRIPTION "High dynamic-range (HDR) image file format" - URL "http://www.openexr.com" + URL "https://www.openexr.com" TYPE OPTIONAL PURPOSE "Required by the Krita OpenEXR filter") macro_bool_to_01(OPENEXR_FOUND HAVE_OPENEXR) set(LINK_OPENEXR_LIB) if(OPENEXR_FOUND) include_directories(SYSTEM ${OPENEXR_INCLUDE_DIR}) set(LINK_OPENEXR_LIB ${OPENEXR_LIBRARIES}) add_definitions(${OPENEXR_DEFINITIONS}) endif() find_package(TIFF) set_package_properties(TIFF PROPERTIES DESCRIPTION "TIFF Library and Utilities" - URL "http://www.remotesensing.org/libtiff" + URL "http://www.libtiff.org" TYPE OPTIONAL PURPOSE "Required by the Krita TIFF filter") if (TIFF_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${TIFF_LIBRARY}) endif() find_package(JPEG) set_package_properties(JPEG PROPERTIES DESCRIPTION "Free library for JPEG image compression. Note: libjpeg8 is NOT supported." - URL "http://www.libjpeg-turbo.org" + URL "https://www.libjpeg-turbo.org" TYPE OPTIONAL PURPOSE "Required by the Krita JPEG filter") if (JPEG_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${JPEG_LIBRARY}) endif() find_package(GIF) set_package_properties(GIF PROPERTIES DESCRIPTION "Library for loading and saving gif files." URL "http://giflib.sourceforge.net/" TYPE OPTIONAL PURPOSE "Required by the Krita GIF filter") if (GIF_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${GIF_LIBRARY}) endif() find_package(HEIF "1.3.0") set_package_properties(HEIF PROPERTIES DESCRIPTION "Library for loading and saving heif files." URL "https://github.com/strukturag/libheif" TYPE OPTIONAL PURPOSE "Required by the Krita HEIF filter") find_package(OpenJPEG "2.3.0") set_package_properties(OpenJPEG PROPERTIES DESCRIPTION "Library for loading and saving jp2000 files." - URL "http://www.openjpeg.org/" + URL "https://www.openjpeg.org/" TYPE OPTIONAL PURPOSE "Required by the Krita JP2000 filter") set(LIBRAW_MIN_VERSION "0.16") find_package(LibRaw ${LIBRAW_MIN_VERSION}) set_package_properties(LibRaw PROPERTIES DESCRIPTION "Library to decode RAW images" - URL "http://www.libraw.org" + URL "https://www.libraw.org/" TYPE OPTIONAL PURPOSE "Required to build the raw import plugin") find_package(FFTW3) set_package_properties(FFTW3 PROPERTIES DESCRIPTION "A fast, free C FFT library" URL "http://www.fftw.org/" TYPE OPTIONAL PURPOSE "Required by the Krita for fast convolution operators and some G'Mic features") macro_bool_to_01(FFTW3_FOUND HAVE_FFTW3) if (FFTW3_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${FFTW3_LIBRARY}) endif() find_package(OCIO) set_package_properties(OCIO PROPERTIES DESCRIPTION "The OpenColorIO Library" - URL "http://www.opencolorio.org" + URL "https://www.opencolorio.org" TYPE OPTIONAL PURPOSE "Required by the Krita LUT docker") macro_bool_to_01(OCIO_FOUND HAVE_OCIO) set_package_properties(PythonLibrary PROPERTIES DESCRIPTION "Python Library" - URL "http://www.python.org" + URL "https://www.python.org" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(PYTHONLIBS_FOUND HAVE_PYTHONLIBS) find_package(SIP "4.19.13") set_package_properties(SIP PROPERTIES DESCRIPTION "Support for generating SIP Python bindings" URL "https://www.riverbankcomputing.com/software/sip/download" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(SIP_FOUND HAVE_SIP) find_package(PyQt5 "5.6.0") set_package_properties(PyQt5 PROPERTIES DESCRIPTION "Python bindings for Qt5." URL "https://www.riverbankcomputing.com/software/pyqt/download5" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(PYQT5_FOUND HAVE_PYQT5) ## ## Look for OpenGL ## # TODO: see if there is a better check for QtGui being built with opengl support (and thus the QOpenGL* classes) if(Qt5Gui_OPENGL_IMPLEMENTATION) message(STATUS "Found QtGui OpenGL support") else() message(FATAL_ERROR "Did NOT find QtGui OpenGL support. Check your Qt configuration. You cannot build Krita without Qt OpenGL support.") endif() ## ## Test for eigen3 ## find_package(Eigen3 3.0 REQUIRED) set_package_properties(Eigen3 PROPERTIES DESCRIPTION "C++ template library for linear algebra" URL "http://eigen.tuxfamily.org" TYPE REQUIRED) ## ## Test for exiv2 ## find_package(LibExiv2 0.16 REQUIRED) if (ANDROID) list (APPEND ANDROID_EXTRA_LIBS ${LibExiv2_LIBRARIES}) # because libexiv2 depends on libexpat and it is installed in the same folder get_filename_component (_base_dir ${LibExiv2_LIBRARIES} DIRECTORY) list (APPEND ANDROID_EXTRA_LIBS ${_base_dir}/libexpat.so) endif() ## ## Test for lcms ## find_package(LCMS2 2.4 REQUIRED) set_package_properties(LCMS2 PROPERTIES DESCRIPTION "LittleCMS Color management engine" URL "http://www.littlecms.com" TYPE REQUIRED PURPOSE "Will be used for color management and is necessary for Krita") if(LCMS2_FOUND) if(NOT ${LCMS2_VERSION} VERSION_LESS 2040 ) set(HAVE_LCMS24 TRUE) endif() set(HAVE_REQUIRED_LCMS_VERSION TRUE) set(HAVE_LCMS2 TRUE) endif() list (APPEND ANDROID_EXTRA_LIBS ${LCMS2_LIBRARIES}) ## ## Test for Vc ## set(OLD_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ) set(HAVE_VC FALSE) if (NOT ${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm") if(NOT MSVC) find_package(Vc 1.1.0) set_package_properties(Vc PROPERTIES DESCRIPTION "Portable, zero-overhead SIMD library for C++" URL "https://github.com/VcDevel/Vc" TYPE OPTIONAL PURPOSE "Required by the Krita for vectorization") macro_bool_to_01(Vc_FOUND HAVE_VC) endif() endif() configure_file(config-vc.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-vc.h ) if(HAVE_VC) message(STATUS "Vc found!") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/vc") include (VcMacros) if(Vc_COMPILER_IS_CLANG) set(ADDITIONAL_VC_FLAGS "-ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() elseif (NOT MSVC) set(ADDITIONAL_VC_FLAGS "-fabi-version=0 -ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() endif() macro(ko_compile_for_all_implementations_no_scalar _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() macro(ko_compile_for_all_implementations _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY Scalar SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() endif() set(CMAKE_MODULE_PATH ${OLD_CMAKE_MODULE_PATH} ) add_definitions(${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS}) ## ## Test endianness ## include (TestBigEndian) test_big_endian(CMAKE_WORDS_BIGENDIAN) ## ## Test for qt-poppler ## find_package(Poppler COMPONENTS Qt5) set_package_properties(Poppler PROPERTIES DESCRIPTION "A PDF rendering library" - URL "http://poppler.freedesktop.org" + URL "https://poppler.freedesktop.org/" TYPE OPTIONAL PURPOSE "Required by the Krita PDF filter.") ## ## Test for quazip ## find_package(QuaZip 0.6) set_package_properties(QuaZip PROPERTIES DESCRIPTION "A library for reading and writing zip files" URL "https://stachenov.github.io/quazip/" TYPE REQUIRED PURPOSE "Needed for reading and writing KRA and ORA files" ) # FIXME: better way to do this? list (APPEND ANDROID_EXTRA_LIBS ${QUAZIP_LIBRARIES} ${EXPAT_LIBRARY} ${KF5_LIBRARIES}/libKF5Completion.so ${KF5_LIBRARIES}/libKF5WindowSystem.so ${KF5_LIBRARIES}/libKF5WidgetsAddons.so ${KF5_LIBRARIES}/libKF5ItemViews.so ${KF5_LIBRARIES}/libKF5ItemModels.so ${KF5_LIBRARIES}/libKF5GuiAddons.so ${KF5_LIBRARIES}/libKF5I18n.so ${KF5_LIBRARIES}/libKF5CoreAddons.so ${KF5_LIBRARIES}/libKF5ConfigGui.so ${KF5_LIBRARIES}/libKF5ConfigCore.so ${KF5_LIBRARIES}/libKF5Archive.so) ## ## Test for Atomics ## include(CheckAtomic) ############################ ############################# ## Add Krita helper macros ## ############################# ############################ include(MacroKritaAddBenchmark) #################### ##################### ## Define includes ## ##################### #################### # for config.h and includes (if any?) include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/interfaces ) add_subdirectory(libs) add_subdirectory(plugins) if (BUILD_TESTING) add_subdirectory(benchmarks) endif() add_subdirectory(krita) configure_file(KoConfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/KoConfig.h ) configure_file(config_convolution.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_convolution.h) configure_file(config-ocio.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ocio.h ) check_function_exists(powf HAVE_POWF) configure_file(config-powf.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-powf.h) if(WIN32) include(${CMAKE_CURRENT_LIST_DIR}/packaging/windows/installer/ConfigureInstallerNsis.cmake) endif() message("\nBroken tests:") foreach(tst ${KRITA_BROKEN_TESTS}) message(" * ${tst}") endforeach() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/po OR EXISTS ${CMAKE_CURRENT_BINARY_DIR}/po ) find_package(KF5I18n CONFIG REQUIRED) ki18n_install(po) endif() if(DEFINED QTANDROID_EXPORTED_TARGET AND NOT TARGET "create-apk") set (_CMAKE_ANDROID_DIR "${ECM_DIR}/../toolchain") list(LENGTH QTANDROID_EXPORTED_TARGET targetsCount) include(${_CMAKE_ANDROID_DIR}/ECMAndroidDeployQt.cmake) math(EXPR last "${targetsCount}-1") foreach(idx RANGE 0 ${last}) list(GET QTANDROID_EXPORTED_TARGET ${idx} exportedTarget) list(GET ANDROID_APK_DIR ${idx} APK_DIR) if(APK_DIR AND NOT EXISTS "${ANDROID_APK_DIR}/AndroidManifest.xml" AND IS_ABSOLUTE ANDROID_APK_DIR) message(FATAL_ERROR "Cannot find ${APK_DIR}/AndroidManifest.xml according to ANDROID_APK_DIR. ${ANDROID_APK_DIR} ${exportedTarget}") elseif(NOT APK_DIR) get_filename_component(_qt5Core_install_prefix "${Qt5Core_DIR}/../../../" ABSOLUTE) set(APK_DIR "${_qt5Core_install_prefix}/src/android/templates/") endif() ecm_androiddeployqt("${exportedTarget}" "${ECM_ADDITIONAL_FIND_ROOT_PATH}") set_target_properties(create-apk-${exportedTarget} PROPERTIES ANDROID_APK_DIR "${APK_DIR}") endforeach() elseif(ANDROID) message(STATUS "You can export a target by specifying -DQTANDROID_EXPORTED_TARGET= and -DANDROID_APK_DIR=") endif() diff --git a/README.md b/README.md index 4c87ca2901..bf67829595 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,51 @@ ![Picture](https://krita.org/wp-content/uploads/2019/04/krita-logo-2019.png) | Jenkins CI Name | Master | Stable | | --------------- | ------ | ------ | | OpenSuse Qt 5.12 | [![Build Status](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20SUSEQt5.12/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20SUSEQt5.12/) |[![Build Status](https://build.kde.org/buildStatus/icon?job=Extragear%2Fkrita%2Fstable-kf5-qt5+SUSEQt5.12)](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20SUSEQt5.12/)| | FreeBSD Qt 5.13 | [![Build Status](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20FreeBSDQt5.13/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20FreeBSDQt5.13/) |[![Build Status](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20FreeBSDQt5.13/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20FreeBSDQt5.13/)| Krita is a free and open source digital painting application. It is for artists who want to create professional work from start to end. Krita is used by comic book artists, illustrators, concept artists, matte and texture painters and in the digital VFX industry. If you are reading this on Github, be aware that this is just a mirror. Our real code repository is provided by KDE: https://invent.kde.org/kde/krita ![Picture](https://krita.org/wp-content/uploads/2016/04/krita-30-screenshot.jpg) ### User Manual https://docs.krita.org/en/user_manual.html ### Development Notes and Build Instructions If you're building on Windows or OSX you'll need to build some third-party dependencies first. You should look at the README in the 3rdparty folder for directions. -If you're building on Linux, please follow David Revoy's Cat Guide: http://www.davidrevoy.com/article193/guide-building-krita-on-linux-for-cats +If you're building on Linux, please follow [the online documentation](https://docs.krita.org/en/untranslatable_pages/building_krita.html). Other developer guides, notes and wiki: https://docs.krita.org/en/untranslatable_pages.html Apidox: https://api.kde.org/extragear-api/graphics-apidocs/krita/html/index.html ### Bugs and Wishes https://bugs.kde.org/buglist.cgi?bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1315444&product=krita&query_format=advanced ### Discussion Forum http://forum.kde.org/viewforum.php?f=136 ### IRC channel Most of the developers hang out here. If you are interested in helping with the project this is a great place to start. Many of the developers based in Europe so they may be offline depending on when you join. irc.freenode.net, #krita ### Project Website http://www.krita.org ### License Krita as a whole is licensed under the GNU Public License, Version 3. Individual files may have a different, but compatible license. diff --git a/build-tools/windows/build.cmd b/build-tools/windows/build.cmd index fb543193e3..bbdf886197 100644 --- a/build-tools/windows/build.cmd +++ b/build-tools/windows/build.cmd @@ -1,818 +1,817 @@ @echo off setlocal enabledelayedexpansion goto begin :: Subroutines :find_on_path out_variable file_name set %1=%~f$PATH:2 goto :EOF :get_dir_path out_variable file_path set %1=%~dp2 goto :EOF :get_full_path out_variable file_path setlocal set FULL_PATH=%~f2 if not exist "%FULL_PATH%" ( set FULL_PATH= ) else ( if exist "%FULL_PATH%\" ( set FULL_PATH= ) ) endlocal & set "%1=%FULL_PATH%" goto :EOF :get_full_path_dir out_variable file_path setlocal set FULL_PATH=%~dp2 if not exist "%FULL_PATH%" ( set FULL_PATH= ) endlocal & set "%1=%FULL_PATH%" goto :EOF :prompt_for_string out_variable prompt set /p %1=%~2^> goto :EOF :prompt_for_positive_integer out_variable prompt setlocal call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" set USER_INPUT=0 set /a RESULT=%USER_INPUT% if not %RESULT% GTR 0 ( set RESULT= ) endlocal & set "%1=%RESULT%" goto :EOF :prompt_for_file out_variable prompt setlocal :prompt_for_file__retry call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" ( endlocal set %1= goto :EOF ) call :get_full_path RESULT "%USER_INPUT%" if "%RESULT%" == "" ( echo Input does not point to valid file! set USER_INPUT= goto prompt_for_file__retry ) endlocal & set "%1=%RESULT%" goto :EOF :prompt_for_dir out_variable prompt setlocal :prompt_for_dir__retry call :prompt_for_string USER_INPUT "%~2" if "%USER_INPUT%" == "" ( endlocal set %1= goto :EOF ) call :get_full_path_dir RESULT "%USER_INPUT%\" if "%RESULT%" == "" ( echo Input does not point to valid dir! set USER_INPUT= goto prompt_for_dir__retry ) endlocal & set "%1=%RESULT%" goto :EOF :usage echo Usage: echo %~n0 [--no-interactive] [ OPTIONS ... ] echo. echo Basic options: echo --no-interactive Run without interactive prompts echo When not specified, the script will prompt echo for some of the parameters. echo --jobs ^ Set parallel jobs count when building echo Defaults to no. of logical cores echo --skip-deps Skips (re)building of deps echo --skip-krita Skips (re)building of Krita echo --cmd Launch a cmd prompt instead of building. echo The environment is set up like the build echo environment with some helper command macros. echo. echo Path options: echo --src-dir ^ Specify Krita source dir echo If unspecified, this will be determined from echo the script location. echo --download-dir ^ Specify deps download dir echo Can be omitted if --skip-deps is used echo --deps-build-dir ^ Specify deps build dir echo Can be omitted if --skip-deps is used echo --deps-install-dir ^ Specify deps install dir echo --krita-build-dir ^ Specify Krita build dir echo Can be omitted if --skip-krita is used echo --krita-install-dir ^ Specify Krita install dir echo Can be omitted if --skip-krita is used echo. goto :EOF :usage_and_exit call :usage exit /b :usage_and_fail call :usage exit /b 100 :: ---------------------------- :begin echo Krita build script for Windows echo. :: command-line args parsing set ARG_NO_INTERACTIVE= set ARG_JOBS= set ARG_SKIP_DEPS= set ARG_SKIP_KRITA= set ARG_SRC_DIR= set ARG_DOWNLOAD_DIR= set ARG_DEPS_BUILD_DIR= set ARG_DEPS_INSTALL_DIR= set ARG_KRITA_BUILD_DIR= set ARG_KRITA_INSTALL_DIR= set ARG_CMD= :args_parsing_loop set CURRENT_MATCHED= if not "%1" == "" ( if "%1" == "--no-interactive" ( set ARG_NO_INTERACTIVE=1 set CURRENT_MATCHED=1 ) if "%1" == "--jobs" ( if not "%ARG_JOBS%" == "" ( echo ERROR: Arg --jobs specified more than once 1>&2 echo. goto usage_and_fail ) set /a "ARG_JOBS=%2" if not !ARG_JOBS! GTR 0 ( echo ERROR: Arg --jobs is not a positive integer 1>&2 echo. goto usage_and_fail ) shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--skip-deps" ( set ARG_SKIP_DEPS=1 set CURRENT_MATCHED=1 ) if "%1" == "--skip-krita" ( set ARG_SKIP_KRITA=1 set CURRENT_MATCHED=1 ) if "%1" == "--src-dir" ( if not "%ARG_SRC_DIR%" == "" ( echo ERROR: Arg --src-dir specified more than once 1>&2 echo. goto usage_and_fail ) if not exist "%~f2\" ( echo ERROR: Arg --src-dir does not point to a directory 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_SRC_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--download-dir" ( if not "%ARG_DOWNLOAD_DIR%" == "" ( echo ERROR: Arg --download-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --download-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DOWNLOAD_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--deps-build-dir" ( if not "%ARG_DEPS_BUILD_DIR%" == "" ( echo ERROR: Arg --deps-build-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --deps-build-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DEPS_BUILD_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--deps-install-dir" ( if not "%ARG_DEPS_INSTALL_DIR%" == "" ( echo ERROR: Arg --deps-install-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --deps-install-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_DEPS_INSTALL_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--krita-build-dir" ( if not "%ARG_KRITA_BUILD_DIR%" == "" ( echo ERROR: Arg --krita-build-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --krita-build-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_KRITA_BUILD_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--krita-install-dir" ( if not "%ARG_KRITA_INSTALL_DIR%" == "" ( echo ERROR: Arg --krita-install-dir specified more than once 1>&2 echo. goto usage_and_fail ) if "%~f2" == "" ( echo ERROR: Arg --krita-install-dir does not point to a valid path 1>&2 echo. goto usage_and_fail ) call :get_dir_path ARG_KRITA_INSTALL_DIR "%~f2\" shift /2 set CURRENT_MATCHED=1 ) if "%1" == "--cmd" ( set ARG_CMD=1 set CURRENT_MATCHED=1 ) if "%1" == "--help" ( goto usage_and_exit ) if not "!CURRENT_MATCHED!" == "1" ( echo ERROR: Unknown option %1 1>&2 echo. goto usage_and_fail ) shift /1 goto args_parsing_loop ) if "%ARG_NO_INTERACTIVE%" == "1" ( echo Non-interactive mode ) else ( echo Interactive mode :: Trick to pause on exit call :real_begin pause exit /b !ERRORLEVEL! ) :real_begin echo. if "%ARG_SKIP_DEPS%" == "1" ( if "%ARG_SKIP_KRITA%" == "1" ( echo ERROR: You cannot skip both deps and Krita 1>&2 echo. exit /b 102 ) echo Building of deps will be skipped. ) else ( if "%ARG_SKIP_KRITA%" == "1" ( echo Building of Krita will be skipped. ) else ( echo Both deps and Krita will be built. ) ) :: Check environment config if "%CMAKE_EXE%" == "" ( call :find_on_path CMAKE_EXE cmake.exe if "!CMAKE_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file CMAKE_EXE "Provide path to cmake.exe" ) if "!CMAKE_EXE!" == "" ( echo ERROR: CMake not found! 1>&2 exit /b 102 ) ) else ( echo Found CMake on PATH: !CMAKE_EXE! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file CMAKE_EXE "Provide path to cmake.exe" if "!CMAKE_EXE!" == "" ( echo ERROR: CMake not found! 1>&2 exit /b 102 ) ) ) ) ) echo CMake: %CMAKE_EXE% if "%SEVENZIP_EXE%" == "" ( call :find_on_path SEVENZIP_EXE 7z.exe if "!SEVENZIP_EXE!" == "" ( set "SEVENZIP_EXE=%ProgramFiles%\7-Zip\7z.exe" if "!SEVENZIP_EXE!" == "" ( set "SEVENZIP_EXE=%ProgramFiles(x86)%\7-Zip\7z.exe" ) if "!SEVENZIP_EXE!" == "" ( echo 7-Zip not found ) ) ) if "%SEVENZIP_EXE%" == "" ( echo 7-Zip: %SEVENZIP_EXE% ) if "%MINGW_BIN_DIR%" == "" ( call :find_on_path MINGW_BIN_DIR_MAKE_EXE mingw32-make.exe if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file MINGW_BIN_DIR_MAKE_EXE "Provide path to mingw32-make.exe of mingw-w64" ) if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( echo ERROR: mingw-w64 not found! 1>&2 exit /b 102 ) call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" ) else ( call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" echo Found mingw on PATH: !MINGW_BIN_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file MINGW_BIN_DIR_MAKE_EXE "Provide path to mingw32-make.exe of mingw-w64" if "!MINGW_BIN_DIR_MAKE_EXE!" == "" ( echo ERROR: mingw-w64 not found! 1>&2 exit /b 102 ) call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!" ) ) ) ) echo mingw-w64: %MINGW_BIN_DIR% if "%PYTHON_BIN_DIR%" == "" ( call :find_on_path PYTHON_BIN_DIR_PYTHON_EXE python.exe if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_file PYTHON_BIN_DIR_PYTHON_EXE "Provide path to python.exe of Python 3.6.2" ) if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" ( echo ERROR: Python not found! 1>&2 exit /b 102 ) call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!" ) else ( call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!" echo Found Python on PATH: !PYTHON_BIN_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_file PYTHON_BIN_DIR_PYTHON_EXE "Provide path to python.exe of Python 3.6.2" if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" ( echo ERROR: Python not found! 1>&2 exit /b 102 ) call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!" ) ) ) ) echo Python: %PYTHON_BIN_DIR% if "%ARG_SKIP_DEPS%" == "1" goto skip_windows_sdk_dir_check if "%WindowsSdkDir%" == "" if not "%ProgramFiles(x86)%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10" if "%WindowsSdkDir%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10" if exist "%WindowsSdkDir%\" ( pushd "%WindowsSdkDir%" if exist "bin\x64\fxc.exe" ( set HAVE_FXC_EXE=1 if "%WindowsSdkVerBinPath%" == "" set "WindowsSdkVerBinPath=%WindowsSdkDir%" ) else ( for /f "delims=" %%a in ('dir /a:d /b "bin\10.*"') do ( if exist "bin\%%a\x64\fxc.exe" ( set HAVE_FXC_EXE=1 if "%WindowsSdkVerBinPath%" == "" set "WindowsSdkVerBinPath=%WindowsSdkDir%\bin\%%a\" ) ) ) popd ) set QT_ENABLE_DYNAMIC_OPENGL=ON if not "%HAVE_FXC_EXE%" == "1" ( set WindowsSdkDir= echo Windows SDK 10 with fxc.exe not found echo Qt will *not* be built with ANGLE ^(dynamic OpenGL^) support. if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( exit /b 102 ) ) set QT_ENABLE_DYNAMIC_OPENGL=OFF ) else echo Windows SDK 10 with fxc.exe found on %WindowsSdkDir% :skip_windows_sdk_dir_check if not "%ARG_JOBS%" == "" ( set "PARALLEL_JOBS=%ARG_JOBS%" ) if "%PARALLEL_JOBS%" == "" ( echo Number of logical CPU cores detected: %NUMBER_OF_PROCESSORS% echo Enabling %NUMBER_OF_PROCESSORS% parallel jobs set PARALLEL_JOBS=%NUMBER_OF_PROCESSORS% if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this correct? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_positive_integer PARALLEL_JOBS "Provide no. of parallel jobs" if "!PARALLEL_JOBS!" == "" ( echo ERROR: Invalid job count! 1>&2 exit /b 102 ) ) ) ) echo Parallel jobs count: %PARALLEL_JOBS% if not "%ARG_SRC_DIR%" == "" ( set "KRITA_SRC_DIR=%ARG_SRC_DIR%" ) if "%KRITA_SRC_DIR%" == "" ( :: Check whether this looks like to be in the source tree set "_temp=%~dp0" if "!_temp:~-21!" == "\build-tools\windows\" ( if exist "!_temp:~0,-21!\CMakeLists.txt" ( if exist "!_temp:~0,-21!\3rdparty\CMakeLists.txt" ( set "KRITA_SRC_DIR=!_temp:~0,-21!\" echo Script is running inside Krita src dir ) ) ) ) if "%KRITA_SRC_DIR%" == "" ( if not "%ARG_NO_INTERACTIVE%" == "1" ( call :prompt_for_dir KRITA_SRC_DIR "Provide path of Krita src dir" ) if "!KRITA_SRC_DIR!" == "" ( echo ERROR: Krita src dir not found! 1>&2 exit /b 102 ) ) echo Krita src: %KRITA_SRC_DIR% if "%ARG_SKIP_DEPS%" == "1" goto skip_deps_args_check if not "%ARG_DOWNLOAD_DIR%" == "" ( set "DEPS_DOWNLOAD_DIR=%ARG_DOWNLOAD_DIR%" ) if "%DEPS_DOWNLOAD_DIR%" == "" ( set DEPS_DOWNLOAD_DIR=%CD%\d\ echo Using default deps download dir: !DEPS_DOWNLOAD_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_DOWNLOAD_DIR "Provide path of depps download dir" ) ) if "!DEPS_DOWNLOAD_DIR!" == "" ( echo ERROR: Deps download dir not set! 1>&2 exit /b 102 ) ) echo Deps download dir: %DEPS_DOWNLOAD_DIR% if not "%ARG_DEPS_BUILD_DIR%" == "" ( set "DEPS_BUILD_DIR=%ARG_DEPS_BUILD_DIR%" ) if "%DEPS_BUILD_DIR%" == "" ( set DEPS_BUILD_DIR=%CD%\b_deps\ echo Using default deps build dir: !DEPS_BUILD_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_BUILD_DIR "Provide path of deps build dir" ) ) if "!DEPS_BUILD_DIR!" == "" ( echo ERROR: Deps build dir not set! 1>&2 exit /b 102 ) ) echo Deps build dir: %DEPS_BUILD_DIR% :skip_deps_args_check if not "%ARG_DEPS_INSTALL_DIR%" == "" ( set "DEPS_INSTALL_DIR=%ARG_DEPS_INSTALL_DIR%" ) if "%DEPS_INSTALL_DIR%" == "" ( set DEPS_INSTALL_DIR=%CD%\i_deps\ echo Using default deps install dir: !DEPS_INSTALL_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir DEPS_INSTALL_DIR "Provide path of deps install dir" ) ) if "!DEPS_INSTALL_DIR!" == "" ( echo ERROR: Deps install dir not set! 1>&2 exit /b 102 ) ) echo Deps install dir: %DEPS_INSTALL_DIR% if "%ARG_SKIP_KRITA%" == "1" goto skip_krita_args_check if not "%ARG_KRITA_BUILD_DIR%" == "" ( set "KRITA_BUILD_DIR=%ARG_KRITA_BUILD_DIR%" ) if "%KRITA_BUILD_DIR%" == "" ( set KRITA_BUILD_DIR=%CD%\b\ echo Using default Krita build dir: !KRITA_BUILD_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir KRITA_BUILD_DIR "Provide path of Krita build dir" ) ) if "!KRITA_BUILD_DIR!" == "" ( echo ERROR: Krita build dir not set! 1>&2 exit /b 102 ) ) echo Krita build dir: %KRITA_BUILD_DIR% if not "%ARG_KRITA_INSTALL_DIR%" == "" ( set "KRITA_INSTALL_DIR=%ARG_KRITA_INSTALL_DIR%" ) if "%KRITA_INSTALL_DIR%" == "" ( set KRITA_INSTALL_DIR=%CD%\i\ echo Using default Krita install dir: !KRITA_INSTALL_DIR! if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is this ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( call :prompt_for_dir KRITA_INSTALL_DIR "Provide path of Krita install dir" ) ) if "!KRITA_INSTALL_DIR!" == "" ( echo ERROR: Krita install dir not set! 1>&2 exit /b 102 ) ) echo Krita install dir: %KRITA_INSTALL_DIR% :skip_krita_args_check echo. if not "%ARG_NO_INTERACTIVE%" == "1" ( choice /c ny /n /m "Is the above ok? [y/n] " if errorlevel 3 exit 255 if not errorlevel 2 ( exit /b 1 ) echo. ) :: Initialize clean PATH set PATH=%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\ set PATH=%MINGW_BIN_DIR%;%PYTHON_BIN_DIR%;%PATH% echo Creating dirs... if NOT "%ARG_SKIP_DEPS%" == "1" ( mkdir %DEPS_DOWNLOAD_DIR% if errorlevel 1 ( if not exist "%DEPS_DOWNLOAD_DIR%\" ( echo ERROR: Cannot create deps download dir! 1>&2 exit /b 103 ) ) mkdir %DEPS_BUILD_DIR% if errorlevel 1 ( if not exist "%DEPS_BUILD_DIR%\" ( echo ERROR: Cannot create deps build dir! 1>&2 exit /b 103 ) ) mkdir %DEPS_INSTALL_DIR% if errorlevel 1 ( if not exist "%DEPS_INSTALL_DIR%\" ( echo ERROR: Cannot create deps install dir! 1>&2 exit /b 103 ) ) ) if NOT "%ARG_SKIP_KRITA%" == "1" ( mkdir %KRITA_BUILD_DIR% if errorlevel 1 ( if not exist "%KRITA_BUILD_DIR%\" ( echo ERROR: Cannot create Krita build dir! 1>&2 exit /b 103 ) ) mkdir %KRITA_INSTALL_DIR% if errorlevel 1 ( if not exist "%KRITA_INSTALL_DIR%\" ( echo ERROR: Cannot create Krita install dir! 1>&2 exit /b 103 ) ) ) echo. set CMAKE_BUILD_TYPE=RelWithDebInfo set QT_ENABLE_DEBUG_INFO=OFF :: Paths for CMake set "BUILDDIR_DOWNLOAD_CMAKE=%DEPS_DOWNLOAD_DIR:\=/%" set "BUILDDIR_DOWNLOAD_CMAKE=%BUILDDIR_DOWNLOAD_CMAKE: =\ %" set "BUILDDIR_DEPS_INSTALL_CMAKE=%DEPS_INSTALL_DIR:\=/%" set "BUILDDIR_DEPS_INSTALL_CMAKE=%BUILDDIR_DEPS_INSTALL_CMAKE: =\ %" set "BUILDDIR_KRITA_INSTALL_CMAKE=%KRITA_INSTALL_DIR:\=/%" set "BUILDDIR_KRITA_INSTALL_CMAKE=%BUILDDIR_KRITA_INSTALL_CMAKE: =\ %" set PATH=%DEPS_INSTALL_DIR%\bin;%PATH% if not "%GETTEXT_SEARCH_PATH%" == "" ( set PATH=!PATH!;!GETTEXT_SEARCH_PATH! ) :: Prepare the CMake command lines set CMDLINE_CMAKE_DEPS="%CMAKE_EXE%" "%KRITA_SRC_DIR%\3rdparty" ^ -DSUBMAKE_JOBS=%PARALLEL_JOBS% ^ -DQT_ENABLE_DEBUG_INFO=%QT_ENABLE_DEBUG_INFO% ^ -DQT_ENABLE_DYNAMIC_OPENGL=%QT_ENABLE_DYNAMIC_OPENGL% ^ -DEXTERNALS_DOWNLOAD_DIR=%BUILDDIR_DOWNLOAD_CMAKE% ^ -DINSTALL_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ -G "MinGW Makefiles" ^ -DUSE_QT_TABLET_WINDOWS=ON ^ -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% set CMDLINE_CMAKE_KRITA="%CMAKE_EXE%" "%KRITA_SRC_DIR%\." ^ -DBoost_DEBUG=OFF ^ -DBOOST_INCLUDEDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/include ^ -DBOOST_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ -DBOOST_LIBRARYDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/lib ^ -DCMAKE_PREFIX_PATH=%BUILDDIR_DEPS_INSTALL_CMAKE% ^ -DCMAKE_INSTALL_PREFIX=%BUILDDIR_KRITA_INSTALL_CMAKE% ^ -DBUILD_TESTING=OFF ^ -DHAVE_MEMORY_LEAK_TRACKER=OFF ^ -DFOUNDATION_BUILD=ON ^ -DUSE_QT_TABLET_WINDOWS=ON ^ - -DHIDE_SAFE_ASSERTS=OFF ^ + -DHIDE_SAFE_ASSERTS=ON ^ -Wno-dev ^ - -G "MinGW Makefiles" ^ - -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE% + -G "MinGW Makefiles" :: Launch CMD prompt if requested if "%ARG_CMD%" == "1" ( doskey cmake-deps=cmd /c "pushd %DEPS_BUILD_DIR% && %CMDLINE_CMAKE_DEPS%" doskey cmake-krita=cmd /c "pushd %KRITA_BUILD_DIR% && %CMDLINE_CMAKE_KRITA%" doskey make-deps=cmd /c "pushd %DEPS_BUILD_DIR% && "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target $*" doskey make-krita=cmd /c "pushd %KRITA_BUILD_DIR% && "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target install -- -j%PARALLEL_JOBS%" echo. title Krita build - %KRITA_SRC_DIR% ^(deps: %DEPS_BUILD_DIR%, krita: %KRITA_BUILD_DIR%^) echo You're now in the build environment. echo The following macros are available: echo cmake-deps echo -- Run CMake for the deps. echo make-deps ^ echo -- Run build for the specified deps target. The target name should echo include the `ext_` prefix, e.g. `ext_qt`. echo cmake-krita echo -- Run CMake for Krita. echo make-krita echo -- Run build for Krita's `install` target. echo. echo For more info, type `doskey /macros` to view the macro commands. cmd /k exit ) if "%ARG_SKIP_DEPS%" == "1" goto skip_build_deps pushd %DEPS_BUILD_DIR% if errorlevel 1 ( echo ERROR: Cannot enter deps build dir! 1>&2 exit /b 104 ) echo Running CMake for deps... @echo on %CMDLINE_CMAKE_DEPS% @if errorlevel 1 ( @echo ERROR: CMake configure failed! 1>&2 @exit /b 104 ) @echo off echo. set EXT_TARGETS=patch png2ico zlib lzma gettext openssl qt boost exiv2 fftw3 eigen3 set EXT_TARGETS=%EXT_TARGETS% ilmbase jpeg lcms2 ocio openexr png tiff gsl vc libraw set EXT_TARGETS=%EXT_TARGETS% giflib freetype poppler kwindowsystem drmingw gmic set EXT_TARGETS=%EXT_TARGETS% python sip pyqt set EXT_TARGETS=%EXT_TARGETS% quazip for %%a in (%EXT_TARGETS%) do ( echo Building ext_%%a... "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target ext_%%a if errorlevel 1 ( echo ERROR: Building of ext_%%a failed! 1>&2 exit /b 105 ) ) echo. echo ******** Built deps ******** popd :skip_build_deps if "%ARG_SKIP_KRITA%" == "1" goto skip_build_krita pushd %KRITA_BUILD_DIR% if errorlevel 1 ( echo ERROR: Cannot enter Krita build dir! 1>&2 exit /b 104 ) echo Running CMake for Krita... @echo on %CMDLINE_CMAKE_KRITA% @if errorlevel 1 ( @echo ERROR: CMake configure failed! 1>&2 @exit /b 104 ) @echo off echo. echo Building Krita... "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target install -- -j%PARALLEL_JOBS% if errorlevel 1 ( echo ERROR: Building of Krita failed! 1>&2 exit /b 105 ) echo. echo ******** Built Krita ******** popd :skip_build_krita echo Krita build completed! diff --git a/krita/data/profiles/elles-icc-profiles/make-elles-profiles.c b/krita/data/profiles/elles-icc-profiles/make-elles-profiles.c index 9e71e8d65e..f7212ee31c 100644 --- a/krita/data/profiles/elles-icc-profiles/make-elles-profiles.c +++ b/krita/data/profiles/elles-icc-profiles/make-elles-profiles.c @@ -1,1653 +1,1653 @@ /* License: * * Code for making well-behaved ICC profiles * Copyright © 2013, 2014, 2015 Elle Stone * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Contact information: * ellestone@ninedegreesbelow.com - * http://ninedegreesbelow.com + * https://ninedegreesbelow.com * * */ /* About the ICC profile header "Platform" tag: * * When creating a profile, LCMS checks to see if the platform is * Windows ('MSFT'). If your platform isn't Windows, LCMS defaults * to using the Apple ('APPL') platform tag for the profile header. * * There is an unofficial Platform * cmsPlatformSignature cmsSigUnices 0x2A6E6978 '*nix'. There is, * however, no LCMS2 API for changing the platform when making a profile. * * So on my own computer, to replace 'APPL' with '*nix' in the header, * I modified the LCMS source file 'cmsio0.c' and recompiled LCMS: * #ifdef CMS_IS_WINDOWS_ * Header.platform= (cmsPlatformSignature) _cmsAdjustEndianess32(cmsSigMicrosoft); * #else * Header.platform= (cmsPlatformSignature) _cmsAdjustEndianess32(cmsSigUnices); * #endif * * */ /* Sample command line to compile this code: * * gcc -g -O0 -Wall -o make-elles-profiles make-elles-profiles.c -llcms2 * * You'll see a lot of harmless warnings about unused variables. * That's because I included variables for profiles that the code * doesn't actually make, in case you might want to make * these profiles for your own use. * * */ #include int main () { /* prints D50 XYZ values from lcms2.h, * mostly to let you know the code did something! * */ printf("D50X, D50Y, D50Z = %1.8f %1.8f %1.8f\n", cmsD50X, cmsD50Y, cmsD50Z); /* ************************** TONE CURVES *************************** */ /* sRGB, Rec709, and labl Tone Reproduction Curves ("TRCs") */ /* About these TRCs: * This code makes V2 and V4 ICC profiles. * For the V4 profiles, which are made using parametric curves, * these TRCs can work in unbounded mode. * For the V2 profiles, the resulting TRC is a 4096-point curve and * cannot work in unbounded mode. - * See http://ninedegreesbelow.com/photography/lcms2-unbounded-mode.html + * See https://ninedegreesbelow.com/photography/lcms2-unbounded-mode.html * for an explanation of unbounded mode ICC profile conversions. * * Also, during ICC profile conversions, * LCMS quantizes images with ICC profiles that have point TRCs. * So use the V4 profile variants for editing images with software * that uses LCMS2 as the Color Management System. * */ /* sRGB TRC */ cmsFloat64Number srgb_parameters[5] = { 2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 0.04045 }; cmsToneCurve *srgb_parametic_curve = cmsBuildParametricToneCurve(NULL, 4, srgb_parameters); cmsToneCurve *srgb_parametric[3] = {srgb_parametic_curve,srgb_parametic_curve,srgb_parametic_curve}; /* LAB "L" (perceptually uniform) TRC */ cmsFloat64Number labl_parameters[5] = { 3.0, 0.862076, 0.137924, 0.110703, 0.080002 }; cmsToneCurve *labl_parametic_curve = cmsBuildParametricToneCurve(NULL, 4, labl_parameters); cmsToneCurve *labl_parametric[3] = {labl_parametic_curve,labl_parametic_curve,labl_parametic_curve}; /* Rec 709 TRC */ cmsFloat64Number rec709_parameters[5] = { 1.0 / 0.45, 1.0 / 1.099, 0.099 / 1.099, 1.0 / 4.5, 0.018 }; cmsToneCurve *rec709_parametic_curve = cmsBuildParametricToneCurve(NULL, 4, rec709_parameters); cmsToneCurve *rec709_parametric[3] = {rec709_parametic_curve,rec709_parametic_curve,rec709_parametic_curve}; /* The following true gamma TRCs work in unbounded mode * for both V2 and V4 profiles, and quantization during ICC profile * conversions is not an issue with either the V2 or V4 variants: */ /* gamma=1.00, linear gamma, "linear light", etc tone response curve */ cmsToneCurve* gamma100[3]; gamma100[0] = gamma100[1] = gamma100[2] = cmsBuildGamma (NULL, 1.00); cmsToneCurve* gamma200[3]; gamma200[0] = gamma200[1] = gamma200[2] = cmsBuildGamma (NULL, 2.00); /* Because of hexadecimal rounding during the profile making process, * the following two gamma values for the profile's TRC are not precisely * preserved when a V2 profile is made. So I used the V2 value for * both V2 and V4 versions of the profiles that use these TRCs. * Otherwise V2 and V4 versions of nominally gamma=1.80 and gamma=2.20 * profiles would have slightly different gamma curves. * */ /* gamma=1.80078125 tone response curve */ /* http://www.color.org/chardata/rgb/ROMMRGB.pdf indicates that * the official tone response curve for ROMM isn't a simple gamma curve * but rather has a linear portion in shadows, just like sRGB. * Most ProPhotoRGB profiles use a gamma curve equal to 1.80078125. * This odd value is because of hexadecimal rounding. * */ cmsToneCurve* gamma180[3]; gamma180[0] = gamma180[1] = gamma180[2] = cmsBuildGamma (NULL, 1.80078125); /* gamma=2.19921875 tone response curve */ -/* per http://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf; +/* per https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf; * ClayRGB uses this value. Based on an old proprietary profile, * it also appears to be the correct gamma for WideGamutRGB. * It also is what you get when you * try to create a V2 profile with a gamma=2.20 gamma curve. * So perhaps the AdobeRGB1998 specifications * were simply bowing to the inevitable hexadecimal rounding. * Almost all (all?) V2 ICC profiles with nominally gamma=2.20 * really have a gamma of 2.19921875, not 2.20. * */ cmsToneCurve* gamma220[3]; gamma220[0] = gamma220[1] = gamma220[2] = cmsBuildGamma (NULL, 2.19921875); /* ************************** WHITE POINTS ************************** */ /* D50 WHITE POINTS */ cmsCIExyY d50_romm_spec= {0.3457, 0.3585, 1.0}; -/* http://photo-lovers.org/pdf/color/romm.pdf */ +/* https://photo-lovers.org/pdf/color/romm.pdf */ cmsCIExyY d50_illuminant_specs = {0.345702915, 0.358538597, 1.0}; /* calculated from D50 illuminant XYZ values in ICC specs */ /* D65 WHITE POINTS */ cmsCIExyY d65_srgb_adobe_specs = {0.3127, 0.3290, 1.0}; /* White point from the sRGB.icm and AdobeRGB1998 profile specs: - * http://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf + * https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf * 4.2.1 Reference Display White Point * The chromaticity coordinates of white displayed on * the reference color monitor shall be x=0.3127, y=0.3290. * . . . [which] correspond to CIE Standard Illuminant D65. * * Wikipedia gives this same white point for SMPTE-C. * This white point is also given in the sRGB color space specs. * It's probably correct for most or all of the standard D65 profiles. * * The D65 white point values used in the LCMS virtual sRGB profile * is slightly different than the D65 white point values given in the * sRGB color space specs, so the LCMS virtual sRGB profile * doesn't match sRGB profiles made using the values given in the * sRGB color space specs. * * */ /* Various C and E WHITE POINTS */ cmsCIExyY c_astm = {0.310060511, 0.316149551, 1.0}; /* see http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html */ cmsCIExyY e_astm = {0.333333333, 0.333333333, 1.0}; /* see http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html */ cmsCIExyY c_cie= {0.310, 0.316}; /* https://en.wikipedia.org/wiki/NTSC#Colorimetry */ cmsCIExyY e_cie= {0.333, 0.333}; cmsCIExyY c_6774_robertson= {0.308548930, 0.324928102, 1.0}; -/* see http://en.wikipedia.org/wiki/Standard_illuminant#White_points_of_standard_illuminants +/* see https://en.wikipedia.org/wiki/Standard_illuminant#White_points_of_standard_illuminants * also see http://www.brucelindbloom.com/index.html?Eqn_T_to_xy.html for the equations */ cmsCIExyY e_5454_robertson= {0.333608970, 0.348572909, 1.0}; -/* see http://en.wikipedia.org/wiki/Standard_illuminant#White_points_of_standard_illuminants +/* see https://en.wikipedia.org/wiki/Standard_illuminant#White_points_of_standard_illuminants * also see http://www.brucelindbloom.com/index.html?Eqn_T_to_xy.html for the equations */ /* ACES white point, taken from * Specification S-2014-004 * ACEScg – A Working Space for CGI Render and Compositing */ cmsCIExyY d60_aces= {0.32168, 0.33767, 1.0}; /* *****************Set up profile variables and values *************** */ cmsHPROFILE profile; cmsToneCurve* tone_curve[3]; cmsCIExyY whitepoint; cmsCIExyYTRIPLE primaries; const char* filename; cmsMLU *copyright = cmsMLUalloc(NULL, 1); /* I put a Creative Commons Attribution-ShareAlike 3.0 Unported License * on the profiles that I distribute (see * https://creativecommons.org/licenses/by-sa/3.0/legalcode) * The CC V3 Attribution-ShareAlike unported licence is accepted by both * Debian and Fedora as a free licence. * * Note that the CC V3 BY-SA licence that I put on the ICC profiles * is not the same licence that's applied to my profile-making code, * which is LGPL2.1+. * * Of course you can modify my profile-making code to put some other * *profile* copyright on the actual profiles that the code makes, * for example public domain (Creative Commons CC0) * or one of the GPL copyrights. * * The ICC suggested wording for profile copyrights is here * (see the last section, entitled "Licensing"): * http://www.color.org/registry/profileregistration.xalter * * The ICC copyright sounds like it's probably a free licence, * but as of June 2015 it hasn't been accepted by Fedora or Debian as a * free license. * * Here are the Fedora and Debian lists of free licences: * * https://fedoraproject.org/wiki/Licensing:Main?rd=Licensing#Content_Licenses * https://wiki.debian.org/DFSGLicenses * * */ cmsMLUsetASCII(copyright, "en", "US", "Copyright 2015, Elle Stone (website: http://ninedegreesbelow.com/; email: ellestone@ninedegreesbelow.com). This ICC profile is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License (https://creativecommons.org/licenses/by-sa/3.0/legalcode)."); cmsMLU *manufacturer = cmsMLUalloc(NULL, 1); cmsMLU *description; /* ********************** MAKE THE PROFILES ************************* */ /* ACES PROFILES */ /* ***** Make profile: ACEScg, D60, gamma=1.00 */ /* ACEScg chromaticities taken from * Specification S-2014-004 * ACEScg – A Working Space for CGI Render and Compositing */ cmsCIExyYTRIPLE aces_cg_primaries = { {0.713, 0.293, 1.0}, {0.165, 0.830, 1.0}, {0.128, 0.044, 1.0} }; whitepoint = d60_aces; primaries = aces_cg_primaries; /* linear gamma */ tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma100[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ACEScg-elle-V4-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ACEScg-elle-V4-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ACEScg-elle-V2-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ACEScg-elle-V2-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* gamma=2.2 */ tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ACEScg-elle-V4-g22.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ACEScg-elle-V4-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ACEScg-elle-V2-g22.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ACEScg-elle-V2-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* sRGB TRC */ tone_curve[0] = tone_curve[1] = tone_curve[2] = srgb_parametric[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ACEScg-elle-V4-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ACEScg-elle-V4-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ACEScg-elle-V2-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ACEScg-elle-V2-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* labl TRC */ tone_curve[0] = tone_curve[1] = tone_curve[2] = labl_parametric[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ACEScg-elle-V4-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ACEScg-elle-V4-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ACEScg-elle-V2-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ACEScg-elle-V2-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* ***** Make profile: ACES, D60, gamma=1.00 */ /* ACES chromaticities taken from * Specification * */ cmsCIExyYTRIPLE aces_primaries = { {0.73470, 0.26530, 1.0}, {0.00000, 1.00000, 1.0}, {0.00010, -0.07700, 1.0} }; cmsCIExyYTRIPLE aces_primaries_prequantized = { {0.734704192222, 0.265298276252, 1.0}, {-0.000004945077, 0.999992850272, 1.0}, {0.000099889199, -0.077007518685, 1.0} }; whitepoint = d60_aces; primaries = aces_primaries_prequantized; /* linear gamma */ tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma100[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ACES-elle-V4-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ACES-elle-V4-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ACES-elle-V2-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ACES-elle-V2-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* gamma=2.2 */ tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ACES-elle-V4-g122.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ACES-elle-V4-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ACES-elle-V2-g22.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ACES-elle-V2-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* sRGB TRC */ tone_curve[0] = tone_curve[1] = tone_curve[2] = srgb_parametric[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ACES-elle-V4-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ACES-elle-V4-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ACES-elle-V2-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ACES-elle-V2-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* labl TRC */ tone_curve[0] = tone_curve[1] = tone_curve[2] = labl_parametric[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ACES-elle-V4-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ACES-elle-V4-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ACES-elle-V2-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ACES-elle-V2-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* D50 PROFILES */ /* ***** Make profile: AllColorsRGB, D50, gamma=1.00 */ /* AllColors.icc has a slightly larger color gamut than the ACES color space. * It has a D50 white point and a linear gamma TRC. * It holds all visible colors. * Just like the ACES color space, * AllColors also holds a high percentage of imaginary colors. - * See http://ninedegreesbelow.com/photography/xyz-rgb.html#xyY + * See https://ninedegreesbelow.com/photography/xyz-rgb.html#xyY * for more information about imaginary colors. * AllColors primaries for red and blue from - * http://www.ledtuning.nl/en/cie-convertor + * https://www.ledtuning.nl/en/cie-convertor * blue 375nm red 780nm, plus Y intercepts: * Color Wavelength (): 375 nm. * Spectral Locus coordinates: X:0.17451 Y:0.005182 * Color Wavelength (): 780 nm. * Spectral Locus coordinates: X:0.734690265 Y:0.265309735 * X1:0.17451 Y1:0.005182 * X2:0.734690265 Y2:0.265309735 * X3:0.00Y3:? Solve for Y3: * (0.265309735-0.005182)/(0.734690265-0.17451)=0.46436433279205221554=m * y=mx+b let x=0; y=b * Y1=0.005182=(0.46436433279205221554*0.17451)+b * b=0.005182-(0.46436433279205221554*0.17451)=-.07585421971554103213 * */ cmsCIExyYTRIPLE allcolors_primaries = { {0.734690265, 0.265309735, 1.0}, {0.000000000, 1.000000000, 1.0}, {0.000000000, -.0758542197, 1.0} }; whitepoint = d50_illuminant_specs; primaries = allcolors_primaries; /* linear gamma */ tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma100[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "AllColorsRGB-elle-V4-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "AllColorsRGB-elle-V4-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "AllColorsRGB-elle-V2-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "AllColorsRGB-elle-V2-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* gamma=2.2 */ tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "AllColorsRGB-elle-V4-g22.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "AllColorsRGB-elle-V4-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "AllColorsRGB-elle-V2-g22.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "AllColorsRGB-elle-V2-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* sRGB TRC */ tone_curve[0] = tone_curve[1] = tone_curve[2] = srgb_parametric[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "AllColorsRGB-elle-V4-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "AllColorsRGB-elle-V4-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "AllColorsRGB-elle-V2-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "AllColorsRGB-elle-V2-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* labl TRC */ tone_curve[0] = tone_curve[1] = tone_curve[2] = labl_parametric[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "AllColorsRGB-elle-V4-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "AllColorsRGB-elle-V4-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "AllColorsRGB-elle-V2-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "AllColorsRGB-elle-V2-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* ***** Make profile: Identity, D50, gamma=1.00. */ /* These primaries also hold all possible visible colors, * but less efficiently than the AllColors profile.*/ cmsCIExyYTRIPLE identity_primaries = {/* */ {1.0, 0.0, 1.0}, {0.0, 1.0, 1.0}, {0.0, 0.0, 1.0} }; whitepoint = d50_illuminant_specs; primaries = identity_primaries; /* linear gamma */ tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma100[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "IdentityRGB-elle-V4-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "IdentityRGB-elle-V4-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "IdentityRGB-elle-V2-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "IdentityRGB-elle-V2-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* gamma=2.2 */ tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "IdentityRGB-elle-V4-g22.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "IdentityRGB-elle-V4-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "IdentityRGB-elle-V2-g22.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "IdentityRGB-elle-V2-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* sRGB TRC */ tone_curve[0] = tone_curve[1] = tone_curve[2] = srgb_parametric[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "IdentityRGB-elle-V4-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "IdentityRGB-elle-V4-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "IdentityRGB-elle-V2-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "IdentityRGB-elle-V2-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* labl TRC */ tone_curve[0] = tone_curve[1] = tone_curve[2] = labl_parametric[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "IdentityRGB-elle-V4-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "IdentityRGB-elle-V4-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "IdentityRGB-elle-V2-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "IdentityRGB-elle-V2-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* ***** Make profile: Romm/Prophoto, D50, gamma=1.80 */ /* Reference Input/Output Medium Metric RGB Color Encodings (RIMM/ROMM RGB) * Kevin E. Spaulding, Geoffrey J. Woolfe and Edward J. Giorgianni * Eastman Kodak Company, Rochester, New York, U.S.A. - * Above document is available at http://photo-lovers.org/pdf/color/romm.pdf + * Above document is available at https://photo-lovers.org/pdf/color/romm.pdf * Kodak designed the Romm (ProPhoto) color gamut to include all printable * and most real world colors. It includes some imaginary colors and excludes * some of the real world blues and violet blues that can be captured by * digital cameras. For high bit depth image editing only. */ cmsCIExyYTRIPLE romm_primaries = { {0.7347, 0.2653, 1.0}, {0.1596, 0.8404, 1.0}, {0.0366, 0.0001, 1.0} }; primaries = romm_primaries; whitepoint = d50_romm_spec; /* gamma 1.80 */ tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma180[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "LargeRGB-elle-V4-g18.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "LargeRGB-elle-V4-g18.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "LargeRGB-elle-V2-g18.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "LargeRGB-elle-V2-g18.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* linear gamma */ tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma100[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "LargeRGB-elle-V4-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "LargeRGB-elle-V4-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "LargeRGB-elle-V2-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "LargeRGB-elle-V2-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* gamma=2.2 */ tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "LargeRGB-elle-V4-g22.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "LargeRGB-elle-V4-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "LargeRGB-elle-V2-g22.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "LargeRGB-elle-V2-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* sRGB TRC */ tone_curve[0] = tone_curve[1] = tone_curve[2] = srgb_parametric[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "LargeRGB-elle-V4-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "LargeRGB-elle-V4-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "LargeRGB-elle-V2-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "LargeRGB-elle-V2-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* labl TRC */ tone_curve[0] = tone_curve[1] = tone_curve[2] = labl_parametric[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "LargeRGB-elle-V4-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "LargeRGB-elle-V4-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "LargeRGB-elle-V2-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "LargeRGB-elle-V2-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* ***** Make profile: WidegamutRGB, D50, gamma=2.19921875 */ /* Pascale's primary values produce a profile that matches * old V2 Widegamut profiles from Adobe and Canon. * Danny Pascale: A review of RGB color spaces * http://www.babelcolor.com/download/A%20review%20of%20RGB%20color%20spaces.pdf * WideGamutRGB was designed by Adobe to be a wide gamut color space that uses * spectral colors as its primaries. For high bit depth image editing only. */ cmsCIExyYTRIPLE widegamut_pascale_primaries = { {0.7347, 0.2653, 1.0}, {0.1152, 0.8264, 1.0}, {0.1566, 0.0177, 1.0} }; primaries = widegamut_pascale_primaries; whitepoint = d50_romm_spec; /* gamma 2.20 */ tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "WideRGB-elle-V4-g22.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "WideRGB-elle-V4-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "WideRGB-elle-V2-g22.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "WideRGB-elle-V2-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* linear gamma */ tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma100[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "WideRGB-elle-V4-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "WideRGB-elle-V4-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "WideRGB-elle-V2-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "WideRGB-elle-V2-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* sRGB TRC */ tone_curve[0] = tone_curve[1] = tone_curve[2] = srgb_parametric[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "WideRGB-elle-V4-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "WideRGB-elle-V4-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "WideRGB-elle-V2-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "WideRGB-elle-V2-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* labl TRC */ tone_curve[0] = tone_curve[1] = tone_curve[2] = labl_parametric[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "WideRGB-elle-V4-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "WideRGB-elle-V4-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "WideRGB-elle-V2-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "WideRGB-elle-V2-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* D65 PROFILES */ /* ***** Make profile: ClayRGB (AdobeRGB), D65, gamma=2.19921875 */ /* The Adobe RGB 1998 color gamut covers a higher percentage of * real-world greens than sRGB, but still doesn't include all printable * greens, yellows, and cyans. * When made using the gamma=2.19921875 tone response curve, * this profile can be used for 8-bit image editing * if used with appropriate caution to avoid posterization. * When made with the gamma=2.19921875 tone response curve * this profile can be applied to DCF R98 camera-generated jpegs. * */ cmsCIExyYTRIPLE adobe_primaries = { {0.6400, 0.3300, 1.0}, {0.2100, 0.7100, 1.0}, {0.1500, 0.0600, 1.0} }; cmsCIExyYTRIPLE adobe_primaries_prequantized = { {0.639996511, 0.329996864, 1.0}, {0.210005295, 0.710004866, 1.0}, {0.149997606, 0.060003644, 1.0} }; primaries = adobe_primaries_prequantized; whitepoint = d65_srgb_adobe_specs; /* gamma 2.20 */ tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ClayRGB-elle-V4-g22.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ClayRGB-elle-V4-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ClayRGB-elle-V2-g22.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ClayRGB-elle-V2-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* linear gamma */ tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma100[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ClayRGB-elle-V4-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ClayRGB-elle-V4-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ClayRGB-elle-V2-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ClayRGB-elle-V2-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* sRGB TRC */ tone_curve[0] = tone_curve[1] = tone_curve[2] = srgb_parametric[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ClayRGB-elle-V4-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ClayRGB-elle-V4-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ClayRGB-elle-V2-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ClayRGB-elle-V2-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* labl TRC */ tone_curve[0] = tone_curve[1] = tone_curve[2] = labl_parametric[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ClayRGB-elle-V4-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ClayRGB-elle-V4-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "ClayRGB-elle-V2-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "ClayRGB-elle-V2-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* ***** Make profile: Rec.2020, D65, Rec709 TRC */ /* * */ cmsCIExyYTRIPLE rec2020_primaries = { {0.7079, 0.2920, 1.0}, {0.1702, 0.7965, 1.0}, {0.1314, 0.0459, 1.0} }; cmsCIExyYTRIPLE rec2020_primaries_prequantized = { {0.708012540607, 0.291993664388, 1.0}, {0.169991652439, 0.797007778423, 1.0}, {0.130997824007, 0.045996550894, 1.0} }; primaries = rec2020_primaries_prequantized; whitepoint = d65_srgb_adobe_specs; /* rec.709 */ tone_curve[0] = tone_curve[1] = tone_curve[2] = rec709_parametric[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Rec2020-elle-V4-rec709.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Rec2020-elle-V4-rec709.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Rec2020-elle-V2-rec709.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Rec2020-elle-V2-rec709.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* linear gamma */ tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma100[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Rec2020-elle-V4-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Rec2020-elle-V4-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Rec2020-elle-V2-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Rec2020-elle-V2-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* gamma=2.2 */ tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Rec2020-elle-V4-g22.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Rec2020-elle-V4-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Rec2020-elle-V2-g22.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Rec2020-elle-V2-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* sRGB TRC */ tone_curve[0] = tone_curve[1] = tone_curve[2] = srgb_parametric[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Rec2020-elle-V4-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Rec2020-elle-V4-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Rec2020-elle-V2-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Rec2020-elle-V2-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* labl TRC */ tone_curve[0] = tone_curve[1] = tone_curve[2] = labl_parametric[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Rec2020-elle-V4-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Rec2020-elle-V4-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Rec2020-elle-V2-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Rec2020-elle-V2-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* ***** Make profile: sRGB, D65, sRGB TRC */ -/* http://en.wikipedia.org/wiki/Srgb */ +/* https://en.wikipedia.org/wiki/Srgb */ /* Hewlett-Packard and Microsoft designed sRGB to match * the color gamut of consumer-grade CRTs from the 1990s * and to be the standard color space for the world wide web. * When made using the standard sRGB TRC, this sRGB profile * can be applied to DCF R03 camera-generated jpegs and * is an excellent color space for editing 8-bit images. * When made using the linear gamma TRC, the resulting profile * should only be used for high bit depth image editing. * */ cmsCIExyYTRIPLE srgb_primaries = { {0.6400, 0.3300, 1.0}, {0.3000, 0.6000, 1.0}, {0.1500, 0.0600, 1.0} }; cmsCIExyYTRIPLE srgb_primaries_pre_quantized = { {0.639998686, 0.330010138, 1.0}, {0.300003784, 0.600003357, 1.0}, {0.150002046, 0.059997204, 1.0} }; primaries = srgb_primaries_pre_quantized; whitepoint = d65_srgb_adobe_specs; /* sRGB TRC */ tone_curve[0] = tone_curve[1] = tone_curve[2] = srgb_parametric[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "sRGB-elle-V4-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "sRGB-elle-V4-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "sRGB-elle-V2-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "sRGB-elle-V2-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* linear gamma */ tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma100[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "sRGB-elle-V4-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "sRGB-elle-V4-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "sRGB-elle-V2-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "sRGB-elle-V2-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* gamma=2.2 */ tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "sRGB-elle-V4-g22.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "sRGB-elle-V4-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "sRGB-elle-V2-g22.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "sRGB-elle-V2-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* labl TRC */ tone_curve[0] = tone_curve[1] = tone_curve[2] = labl_parametric[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "sRGB-elle-V4-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "sRGB-elle-V4-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "sRGB-elle-V2-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "sRGB-elle-V2-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* Rec.709 TRC */ tone_curve[0] = tone_curve[1] = tone_curve[2] = rec709_parametric[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "sRGB-elle-V4-rec709.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "sRGB-elle-V4-rec709.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "sRGB-elle-V2-rec709.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "sRGB-elle-V2-rec709.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* ***** Make profile: CIE-RGB profile, E white point*/ /* The ASTM E white point is probably the right white point * to use when making the CIE-RGB color space profile. * It's not clear to me what the correct CIE-RGB primaries really are. * Lindbloom gives one set. The LCMS version 1 tutorial gives a different set. * I asked a friend to ask an expert, who said the real primaries should * be calculated from the spectral wavelengths. * Two sets of primaries are given below: * */ /* This page explains what the CIE color space is: * https://en.wikipedia.org/wiki/CIE_1931 * These pages give the wavelengths: * http://hackipedia.org/Color%20space/pdf/CIE%20Color%20Space.pdf * http://infocom.ing.uniroma1.it/~gaetano/texware/Full-How%20the%20CIE%201931%20Color-Matching%20Functions%20Were%20Derived%20from%20Wright-Guild%20Data.pdf * This page has resources for calculating xy values given a spectral color wavelength: * http://www.cvrl.org/cmfs.htm * This page does the calculations for you: - * http://www.ledtuning.nl/cie.php + * https://www.ledtuning.nl/en/cie-convertor * Plugging the wavelengths into the ledtuning website * gives the following CIE RGB xy primaries: 700.0 nm has Spectral Locus coordinates: x:0.734690023 y:0.265309977 546.1 nm has Spectral Locus coordinates: x:0.2736747378 y:0.7174284409 435.8 nm has Spectral Locus coordinates: x:0.1665361196 y:0.0088826412 * */ cmsCIExyYTRIPLE cie_primaries_ledtuning = { {0.7346900230, 0.2653099770, 1.0}, {0.2736747378, 0.7174284409, 1.0}, {0.1665361196, 0.0088826412, 1.0} }; /* Assuming you want to use the ASTM values for the E white point, * here are the prequantized ledtuning primaries */ cmsCIExyYTRIPLE cie_primaries_ledtuning_prequantized = { {0.734689082, 0.265296653, 1.0}, {0.273673341, 0.717437354, 1.0}, {0.166531028, 0.008882428, 1.0} }; primaries = cie_primaries_ledtuning_prequantized; whitepoint = e_astm; tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "CIERGB-elle-V4-g22.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "CIERGB-elle-V4-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "CIERGB-elle-V2-g22.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "CIERGB-elle-V2-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* A linear gamma version of this profile makes more sense * than a gamma=2.2 version */ tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma100[0]; whitepoint = e_astm; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "CIERGB-elle-V4-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "CIERGB-elle-V4-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "CIERGB-elle-V2-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "CIERGB-elle-V2-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* sRGB TRC*/ tone_curve[0] = tone_curve[1] = tone_curve[2] = srgb_parametric[0]; whitepoint = e_astm; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "CIERGB-elle-V4-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "CIERGB-elle-V4-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "CIERGB-elle-V2-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "CIERGB-elle-V2-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* labl TRC */ tone_curve[0] = tone_curve[1] = tone_curve[2] = labl_parametric[0]; profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "CIERGB-elle-V4-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "CIERGB-elle-V4-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "CIERGB-elle-V2-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "CIERGB-elle-V2-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* * Here's the second set of primaries * http://www.cis.rit.edu/research/mcsl2/research/broadbent/CIE1931_RGB.pdf * https://groups.google.com/forum/#!topic/sci.engr.color/fBI3k1llm-g * Lindbloom gives these values on his Working Space Information page: */ cmsCIExyYTRIPLE cie_primaries_lindbloom = { {0.7350, 0.2650, 1.0}, {0.2740, 0.7170, 1.0}, {0.1670, 0.0090, 1.0} }; /* Assuming you want to use the ASTM values for the E white point, * here are the prequantized Lindbloom primaries */ cmsCIExyYTRIPLE cie_primaries_lindbloom_prequantized = { {0.714504840, 0.297234644, 1.0}, {0.520085568, 0.452364535, 1.0}, {0.090957433, 0.051485032, 1.0} }; /* ***** Make profile: Gray ICC profiles - D50 white point ********* */ whitepoint = d50_illuminant_specs; const cmsToneCurve* grayTRC; /* Gray profile with gamma=1.00, linear gamma, "linear light", etc */ grayTRC = cmsBuildGamma (NULL, 1.00); profile = cmsCreateGrayProfile ( &whitepoint, grayTRC ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V4-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Gray-D50-elle-V4-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V2-g10.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Gray-D50-elle-V2-g10.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* Gray profile with gamma=1.80, * actually 1.80078125, * in order to create the same gamma curve in V2 and V4 profiles */ grayTRC = cmsBuildGamma (NULL, 1.80078125); profile = cmsCreateGrayProfile ( &whitepoint, grayTRC ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V4-g18.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Gray-D50-elle-V4-g18.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V2-g18.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Gray-D50-elle-V2-g18.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* Gray profile with gamma=2.20 * actually gamma=2.19921875, * in order to create the same gamma curve in V2 and V4 profiles */ grayTRC = cmsBuildGamma (NULL, 2.19921875); profile = cmsCreateGrayProfile ( &whitepoint, grayTRC ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V4-g22.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Gray-D50-elle-V4-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V2-g22.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Gray-D50-elle-V2-g22.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* Gray profile with srgb-trc */ grayTRC = cmsBuildParametricToneCurve(NULL, 4, srgb_parameters); profile = cmsCreateGrayProfile ( &whitepoint, grayTRC ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V4-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Gray-D50-elle-V4-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V2-srgbtrc.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Gray-D50-elle-V2-srgbtrc.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* Gray profile with labl TRC */ grayTRC = cmsBuildParametricToneCurve(NULL, 4, labl_parameters); profile = cmsCreateGrayProfile ( &whitepoint, grayTRC ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V4-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Gray-D50-elle-V4-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V2-labl.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Gray-D50-elle-V2-labl.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* Gray profile with Rec709 TRC */ grayTRC = cmsBuildParametricToneCurve(NULL, 4, rec709_parameters); profile = cmsCreateGrayProfile ( &whitepoint, grayTRC ); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); /* V4 */ description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V4-rec709.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Gray-D50-elle-V4-rec709.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* V2 */ cmsSetProfileVersion(profile, 2.1); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V2-rec709.icc"); cmsWriteTag(profile, cmsSigProfileDescriptionTag, description); filename = "Gray-D50-elle-V2-rec709.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* ***** Make profile: LCMS built-in LAB and XYZ profiles *********** */ /* Based on transicc output, the V4 profiles * can be used in unbounded mode, but the V2 versions cannot. */ profile = cmsCreateLab2Profile(&d50_illuminant_specs); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Lab-D50-Identity-elle-V2.icc"); filename = "Lab-D50-Identity-elle-V2.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); profile = cmsCreateLab4Profile(&d50_illuminant_specs); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "Lab-D50-Identity-elle-V4.icc"); filename = "Lab-D50-Identity-elle-V4.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); profile = cmsCreateXYZProfile(); cmsWriteTag(profile, cmsSigCopyrightTag, copyright); description = cmsMLUalloc(NULL, 1); cmsMLUsetASCII(description, "en", "US", "XYZ-D50-Identity-elle-V4.icc"); filename = "XYZ-D50-Identity-elle-V4.icc"; cmsSaveProfileToFile(profile, filename); cmsMLUfree(description); /* For the following profiles, information is provided, but not the actual * profile making code. * */ /* old monitor-based editing profiles */ /* ColorMatchRGB, D50, gamma=1.80 */ -/* http://www.dpreview.com/forums/post/3902882 - * http://lists.apple.com/archives/colorsync-users/2001/Apr/msg00073.html +/* https://www.dpreview.com/forums/post/3902882 + * https://lists.apple.com/archives/colorsync-users/2001/Apr/msg00073.html * ColorMatch was designed to fit Radius PressView CRT monitors, * similar to sRGB fitting consumer-grade CRT monitors, * "fit" meaning "could be calibrated to match". * Adobe does still distribute a ColorMatchRGB profile. * Making this profile using the D50_romm_doc white point that is used * in other old V2 profiles (ProPhoto, WideGamut) * doesn't make a well behaved profile, * but the resulting profile is very close to the Adobe-supplied version. * Using the prequantized primaries makes a profile that's just as close * to the Adobe-supplied version and in addition is well behaved. * Unless you have untagged images created on a PressView CRT, * there is no reason to make or use this profile. * */ cmsCIExyYTRIPLE colormatch_primaries = { {0.6300, 0.3400, 1.0}, {0.2950, 0.6050, 1.0}, {0.1500, 0.0750, 1.0} }; cmsCIExyYTRIPLE colormatch_primaries_prequantized = { {0.629992636, 0.339999723, 1.0}, {0.295006332, 0.604997745, 1.0}, {0.149992036, 0.075005244, 1.0} }; primaries = colormatch_primaries_prequantized; whitepoint = d50_romm_spec; tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma180[0]; /* AppleRGB, D65, gamma=1.80 */ /* AppleRGB was created to fit the old Apple CRT displays * just as sRGB fit consumer-grade CRT monitors * and ColorMatch fit PressView CRT monitors. * */ cmsCIExyYTRIPLE apple_primaries = { {0.6250, 0.3400, 1.0}, {0.2800, 0.5950, 1.0}, {0.1550, 0.0700, 1.0} }; cmsCIExyYTRIPLE apple_primaries_prequantized = { {0.625012368, 0.340000081, 1.0}, {0.279996113, 0.595006943, 1.0}, {0.155001212, 0.070001183, 1.0} }; primaries = apple_primaries_prequantized; whitepoint = d65_srgb_adobe_specs; tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma180[0]; /* video profiles */ /* PAL-SECAM, D65 gamma=2.20 */ -/* http://en.wikipedia.org/wiki/PAL */ +/* https://en.wikipedia.org/wiki/PAL */ cmsCIExyYTRIPLE pal_primaries = { {0.6400, 0.3300, 1.0}, {0.2900, 0.6000, 1.0}, {0.1500, 0.0600, 1.0} }; /* PAL is one of many video and television-related color spaces. * If you need the original profile with all its tags, * I recommend that you use the Argyllcms version of this profile * (EBU3213_PAL.icm, located in the "ref" folder), * rather than making your own. * But if you do want to make your own PAL profile using LCMS2, * these prequantized primaries and white point make a profile with * the same primaries and white point as the Argyllcms profile. * The Argyllcms profile has a point curve TRC. * */ cmsCIExyYTRIPLE pal_primaries_prequantized = { {0.640007798, 0.330006592, 1.0}, {0.290000327, 0.600000840, 1.0}, {0.149998025, 0.059996098, 1.0} }; primaries = pal_primaries_prequantized; whitepoint = d65_srgb_adobe_specs; tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0]; /* SMPTE-C, D65, gamma=2.20 */ -/* http://en.wikipedia.org/wiki/NTSC#SMPTE_C +/* https://en.wikipedia.org/wiki/NTSC#SMPTE_C * SMPTE-C is one of many video and television-related color spaces * and is an update of the original NTSC. */ cmsCIExyYTRIPLE smpte_c_primaries = { {0.6300, 0.3400, 1.0}, {0.3100, 0.5950, 1.0}, {0.1550, 0.0700, 1.0} }; cmsCIExyYTRIPLE smpte_c_primaries_prequantized = { {0.629996495, 0.339990597, 1.0}, {0.309997880, 0.594995808, 1.0}, {0.149999952, 0.069999431, 1.0} }; primaries = smpte_c_primaries_prequantized; whitepoint = d65_srgb_adobe_specs; tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0]; /* NTSC, C, gamma=2.20 */ -/* http://en.wikipedia.org/wiki/NTSC#Colorimetry*/ +/* https://en.wikipedia.org/wiki/NTSC#Colorimetry*/ /* According to Wikipedia, these "original 1953 color NTSC specifications" * were used by early television receivers. */ cmsCIExyYTRIPLE ntcs_primaries = { {0.6700, 0.3300, 1.0}, {0.2100, 0.7100, 1.0}, {0.1400, 0.0800, 1.0} }; cmsCIExyYTRIPLE ntcs_primaries_prequantized = { {0.670010373, 0.330001186, 1.0}, {0.209999261, 0.710001124, 1.0}, {0.139996061, 0.080002934, 1.0} }; primaries = ntcs_primaries_prequantized; whitepoint = c_astm; tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0]; /* *********************** wrap up and close out ****************** */ /* free copyright */ cmsMLUfree(copyright); /* make gcc happy by returning an integer from main() */ return 0; } diff --git a/krita/data/templates/animation/.directory b/krita/data/templates/animation/.directory index 6cbbe623b2..d8cd91b610 100644 --- a/krita/data/templates/animation/.directory +++ b/krita/data/templates/animation/.directory @@ -1,29 +1,30 @@ [Desktop Entry] Name=Animation Templates Name[ar]=قوالب الحركات Name[ca]=Plantilles d'animació Name[ca@valencia]=Plantilles d'animació Name[cs]=Šablony animací: Name[de]=Animations-Vorlagen Name[el]=Πρότυπα εφέ κίνησης Name[en_GB]=Animation Templates Name[es]=Plantillas de animación +Name[et]=Animatsioonimallid Name[eu]=Animazio-txantiloiak Name[fi]=Animaatiopohjat Name[fr]=Modèles pour animation Name[gl]=Modelos de animación Name[is]=Sniðmát fyrir hreyfimyndir Name[it]=Modelli di animazioni Name[ko]=애니메이션 템플릿 Name[nl]=Animatiesjablonen Name[nn]=Animasjonsmalar Name[pl]=Szablony animacji Name[pt]=Modelos de Animações Name[pt_BR]=Modelos de animação Name[sv]=Animeringsmallar Name[tr]=Canlandırma Şablonları Name[uk]=Шаблони анімацій Name[x-test]=xxAnimation Templatesxx Name[zh_CN]=动画模板 Name[zh_TW]=動畫範本 X-KDE-DefaultTab=true diff --git a/krita/krita.action b/krita/krita.action index d575792eb0..6b23a5b09c 100644 --- a/krita/krita.action +++ b/krita/krita.action @@ -1,3652 +1,3665 @@ General Open Resources Folder Opens a file browser at the location Krita saves resources such as brushes to. Opens a file browser at the location Krita saves resources such as brushes to. Open Resources Folder 0 0 false Cleanup removed files... Cleanup removed files Cleanup removed files 0 0 false C&ascade Cascade Cascade 10 0 false &Tile Tile Tile 10 0 false Create Resource Bundle... Create Resource Bundle Create Resource Bundle 0 0 false Show File Toolbar Show File Toolbar Show File Toolbar false Show color selector Show color selector Show color selector Shift+I false Show MyPaint shade selector Show MyPaint shade selector Show MyPaint shade selector Shift+M false Show minimal shade selector Show minimal shade selector Show minimal shade selector Shift+N false Show color history Show color history Show color history H false Show common colors Show common colors Show common colors U false Show Tool Options Show Tool Options Show Tool Options \ false Show Brush Editor Show Brush Editor Show Brush Editor F5 false Show Brush Presets Show Brush Presets Show Brush Presets F6 false Toggle Tablet Debugger Toggle Tablet Debugger Toggle Tablet Debugger 0 0 Ctrl+Shift+T false + Show Krita log for bug reports. + + Show Krita log for bug reports. + Show Krita log for bug reports. + + false + + + + + + + Show system information for bug reports. Show system information for bug reports. Show system information for bug reports. false Rename Composition... Rename Composition Rename Composition 0 0 false Update Composition Update Composition Update Composition 0 0 false Use multiple of 2 for pixel scale Use multiple of 2 for pixel scale Use multiple of 2 for pixel scale Use multiple of 2 for pixel scale 1 0 true &Invert Selection Invert current selection Invert Selection 10000000000 100 Ctrl+Shift+I false Create Snapshot Create Snapshot 1 0 false Switch to Selected Snapshot Switch to selected snapshot 1 0 false Remove Selected Snapshot Remove Selected Snapshot 1 0 false Painting lightness-increase Make brush color lighter Make brush color lighter Make brush color lighter 0 0 L false lightness-decrease Make brush color darker Make brush color darker Make brush color darker 0 0 K false Make brush color more saturated Make brush color more saturated Make brush color more saturated false Make brush color more desaturated Make brush color more desaturated Make brush color more desaturated false Shift brush color hue clockwise Shift brush color hue clockwise Shift brush color hue clockwise false Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise false Make brush color more red Make brush color more red Make brush color more red false Make brush color more green Make brush color more green Make brush color more green false Make brush color more blue Make brush color more blue Make brush color more blue false Make brush color more yellow Make brush color more yellow Make brush color more yellow false opacity-increase Increase opacity Increase opacity Increase opacity 0 0 O false opacity-decrease Decrease opacity Decrease opacity Decrease opacity 0 0 I false draw-eraser Set eraser mode Set eraser mode Set eraser mode 10000 0 E true view-refresh Reload Original Preset Reload Original Preset Reload Original Preset 10000 false transparency-unlocked Preserve Alpha Preserve Alpha Preserve Alpha 10000 true transform_icons_penPressure Use Pen Pressure Use Pen Pressure Use Pen Pressure 10000 true symmetry-horizontal Horizontal Mirror Tool Horizontal Mirror Tool Horizontal Mirror Tool 0 true symmetry-vertical Vertical Mirror Tool Vertical Mirror Tool Vertical Mirror Tool 0 true Hide Mirror X Line Hide Mirror X Line Hide Mirror X Line 10000 true Hide Mirror Y Line Hide Mirror Y Line Hide Mirror Y Line 10000 true Lock Lock X Line Lock X Line 10000 true Lock Y Line Lock Y Line Lock Y Line 10000 true Move to Canvas Center X Move to Canvas Center X Move to Canvas Center X 10000 false Move to Canvas Center Y Move to Canvas Center Y Move to Canvas Center Y 10000 false &Toggle Selection Display Mode Toggle Selection Display Mode Toggle Selection Display Mode 0 0 false Next Favourite Preset Next Favourite Preset Next Favourite Preset , false Previous Favourite Preset Previous Favourite Preset Previous Favourite Preset . false preset-switcher Switch to Previous Preset Switch to Previous Preset Switch to Previous Preset / false Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar true Reset Foreground and Background Color Reset Foreground and Background Color Reset Foreground and Background Color D false Swap Foreground and Background Color Swap Foreground and Background Color Swap Foreground and Background Color X false Selection Mode: Add Selection Mode: Add Selection Mode: Add A false Selection Mode: Subtract Selection Mode: Subtract Selection Mode: Subtract S false Selection Mode: Intersect Selection Mode: Intersect Selection Mode: Intersect false Selection Mode: Replace Selection Mode: Replace Selection Mode: Replace R false smoothing-weighted Brush Smoothing: Weighted Brush Smoothing: Weighted Brush Smoothing: Weighted false smoothing-no Brush Smoothing: Disabled Brush Smoothing: Disabled Brush Smoothing: Disabled false smoothing-stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer false brushsize-decrease Decrease Brush Size Decrease Brush Size Decrease Brush Size 0 0 [ false smoothing-basic Brush Smoothing: Basic Brush Smoothing: Basic Brush Smoothing: Basic false brushsize-increase Increase Brush Size Increase Brush Size Increase Brush Size 0 0 ] false Toggle Assistant Toggle Assistant ToggleAssistant Ctrl+Shift+L true Undo Polygon Selection Points Undo Polygon Selection Points Undo Polygon Selection Points Shift+Z false Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) 10000 1 Ctrl+Shift+Backspace false Fill with Background Color (Opacity) Fill with Background Color (Opacity) Fill with Background Color (Opacity) 10000 1 Ctrl+Backspace false Fill with Pattern (Opacity) Fill with Pattern (Opacity) Fill with Pattern (Opacity) 10000 1 false Convert &to Shape Convert to Shape Convert to Shape 10000000000 0 false &Show Global Selection Mask Shows global selection as a usual selection mask in <interface>Layers</interface> docker Show Global Selection Mask 100000 100 true Filters color-to-alpha &Color to Alpha... Color to Alpha Color to Alpha 10000 0 false &Top Edge Detection Top Edge Detection Top Edge Detection 10000 0 false &Index Colors... Index Colors Index Colors 10000 0 false Emboss Horizontal &Only Emboss Horizontal Only Emboss Horizontal Only 10000 0 false D&odge Dodge Dodge 10000 0 false &Sharpen Sharpen Sharpen 10000 0 false B&urn Burn Burn 10000 0 false &Mean Removal Mean Removal Mean Removal 10000 0 false &Gaussian Blur... Gaussian Blur Gaussian Blur 10000 0 false Emboss &in All Directions Emboss in All Directions Emboss in All Directions 10000 0 false &Small Tiles... Small Tiles Small Tiles 10000 0 false &Levels... Levels Levels 10000 0 Ctrl+L false &Sobel... Sobel Sobel 10000 0 false &Wave... Wave Wave 10000 0 false &Motion Blur... Motion Blur Motion Blur 10000 0 false &Invert Invert Invert 10000 0 Ctrl+I false &Color Adjustment curves... Color Adjustment curves Color Adjustment curves 10000 0 Ctrl+M false Pi&xelize... Pixelize Pixelize 10000 0 false Emboss (&Laplacian) Emboss (Laplacian) Emboss (Laplacian) 10000 0 false &Left Edge Detection Left Edge Detection Left Edge Detection 10000 0 false &Blur... Blur Blur 10000 0 false &Raindrops... Raindrops Raindrops 10000 0 false &Bottom Edge Detection Bottom Edge Detection Bottom Edge Detection 10000 0 false &Random Noise... Random Noise Random Noise 10000 0 false &Brightness/Contrast curve... Brightness/Contrast curve Brightness/Contrast curve 10000 0 false Colo&r Balance... Color Balance Color Balance 10000 0 Ctrl+B false &Phong Bumpmap... Phong Bumpmap Phong Bumpmap 10000 0 false &Desaturate Desaturate Desaturate 10000 0 Ctrl+Shift+U false Color &Transfer... Color Transfer Color Transfer 10000 0 false Emboss &Vertical Only Emboss Vertical Only Emboss Vertical Only 10000 0 false &Lens Blur... Lens Blur Lens Blur 10000 0 false M&inimize Channel Minimize Channel Minimize Channel 10000 0 false M&aximize Channel Maximize Channel Maximize Channel 10000 0 false &Oilpaint... Oilpaint Oilpaint 10000 0 false &Right Edge Detection Right Edge Detection Right Edge Detection 10000 0 false &Auto Contrast Auto Contrast Auto Contrast 10000 0 false &Round Corners... Round Corners Round Corners 10000 0 false &Unsharp Mask... Unsharp Mask Unsharp Mask 10000 0 false &Emboss with Variable Depth... Emboss with Variable Depth Emboss with Variable Depth 10000 0 false Emboss &Horizontal && Vertical Emboss Horizontal & Vertical Emboss Horizontal & Vertical 10000 0 false Random &Pick... Random Pick Random Pick 10000 0 false &Gaussian Noise Reduction... Gaussian Noise Reduction Gaussian Noise Reduction 10000 0 false &Posterize... Posterize Posterize 10000 0 false &Wavelet Noise Reducer... Wavelet Noise Reducer Wavelet Noise Reducer 10000 0 false &HSV Adjustment... HSV Adjustment HSV Adjustment 10000 0 Ctrl+U false Tool Shortcuts Dynamic Brush Tool Dynamic Brush Tool Dynamic Brush Tool false Crop Tool Crop the image to an area Crop the image to an area C false Polygon Tool Polygon Tool. Shift-mouseclick ends the polygon. Polygon Tool. Shift-mouseclick ends the polygon. false Rectangle Tool Rectangle Tool Rectangle Tool false Multibrush Tool Multibrush Tool Multibrush Tool Q false Colorize Mask Tool Colorize Mask Tool Colorize Mask Tool Smart Patch Tool Smart Patch Tool Smart Patch Tool Pan Tool Pan Tool Pan Tool Select Shapes Tool Select Shapes Tool Select Shapes Tool false Color Picker Select a color from the image or current layer Select a color from the image or current layer P false Outline Selection Tool Outline Selection Tool Outline Selection Tool false Bezier Curve Selection Tool Bezier Curve Selection Tool Bezier Curve Selection Tool false Similar Color Selection Tool Similar Color Selection Tool Similar Color Selection Tool false Fill Tool Fill a contiguous area of color with a color, or fill a selection. Fill a contiguous area of color with a color, or fill a selection. F false Line Tool Line Tool Line Tool false Freehand Path Tool Freehand Path Tool Freehand Path Tool false Bezier Curve Tool Bezier Curve Tool. Shift-mouseclick or double-click ends the curve. Bezier Curve Tool. Shift-mouseclick or double-click ends the curve. false Ellipse Tool Ellipse Tool Ellipse Tool false Freehand Brush Tool Freehand Brush Tool Freehand Brush Tool B false Create object Create object Create object false Elliptical Selection Tool Elliptical Selection Tool Elliptical Selection Tool J false Contiguous Selection Tool Contiguous Selection Tool Contiguous Selection Tool false Pattern editing Pattern editing Pattern editing false Review Review Review false Draw a gradient. Draw a gradient. Draw a gradient. G false Polygonal Selection Tool Polygonal Selection Tool Polygonal Selection Tool false Measurement Tool Measure the distance between two points Measure the distance between two points false Rectangular Selection Tool Rectangular Selection Tool Rectangular Selection Tool Ctrl+R false Move Tool Move a layer Move a layer T false Vector Image Tool Vector Image (EMF/WMF/SVM/SVG) tool Vector Image (EMF/WMF/SVM/SVG) tool false Calligraphy Calligraphy Calligraphy false Path editing Path editing Path editing false Zoom Tool Zoom Tool Zoom Tool false Polyline Tool Polyline Tool. Shift-mouseclick ends the polyline. Polyline Tool. Shift-mouseclick ends the polyline. false Transform Tool Transform a layer or a selection Transform a layer or a selection Ctrl+T false Assistant Tool Assistant Tool Assistant Tool false Gradient Editing Tool Gradient editing Gradient editing false Reference Images Tool Reference Images Tool Reference Images Tool false Blending Modes Select Normal Blending Mode Select Normal Blending Mode Select Normal Blending Mode 0 0 Alt+Shift+N false Select Dissolve Blending Mode Select Dissolve Blending Mode Select Dissolve Blending Mode 0 0 Alt+Shift+I false Select Behind Blending Mode Select Behind Blending Mode Select Behind Blending Mode 0 0 Alt+Shift+Q false Select Clear Blending Mode Select Clear Blending Mode Select Clear Blending Mode 0 0 Alt+Shift+R false Select Darken Blending Mode Select Darken Blending Mode Select Darken Blending Mode 0 0 Alt+Shift+K false Select Multiply Blending Mode Select Multiply Blending Mode Select Multiply Blending Mode 0 0 Alt+Shift+M false Select Color Burn Blending Mode Select Color Burn Blending Mode Select Color Burn Blending Mode 0 0 Alt+Shift+B false Select Linear Burn Blending Mode Select Linear Burn Blending Mode Select Linear Burn Blending Mode 0 0 Alt+Shift+A false Select Lighten Blending Mode Select Lighten Blending Mode Select Lighten Blending Mode 0 0 Alt+Shift+G false Select Screen Blending Mode Select Screen Blending Mode Select Screen Blending Mode 0 0 Alt+Shift+S false Select Color Dodge Blending Mode Select Color Dodge Blending Mode Select Color Dodge Blending Mode 0 0 Alt+Shift+D false Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode 0 0 Alt+Shift+W false Select Overlay Blending Mode Select Overlay Blending Mode Select Overlay Blending Mode 0 0 Alt+Shift+O false Select Hard Overlay Blending Mode Select Hard Overlay Blending Mode Select Hard Overlay Blending Mode 0 0 Alt+Shift+P false Select Soft Light Blending Mode Select Soft Light Blending Mode Select Soft Light Blending Mode 0 0 Alt+Shift+F false Select Hard Light Blending Mode Select Hard Light Blending Mode Select Hard Light Blending Mode 0 0 Alt+Shift+H false Select Vivid Light Blending Mode Select Vivid Light Blending Mode Select Vivid Light Blending Mode 0 0 Alt+Shift+V false Select Linear Light Blending Mode Select Linear Light Blending Mode Select Linear Light Blending Mode 0 0 Alt+Shift+J false Select Pin Light Blending Mode Select Pin Light Blending Mode Select Pin Light Blending Mode 0 0 Alt+Shift+Z false Select Hard Mix Blending Mode Select Hard Mix Blending Mode Select Hard Mix Blending Mode 0 0 Alt+Shift+L false Select Difference Blending Mode Select Difference Blending Mode Select Difference Blending Mode 0 0 Alt+Shift+E false Select Exclusion Blending Mode Select Exclusion Blending Mode Select Exclusion Blending Mode 0 0 Alt+Shift+X false Select Hue Blending Mode Select Hue Blending Mode Select Hue Blending Mode 0 0 Alt+Shift+U false Select Saturation Blending Mode Select Saturation Blending Mode Select Saturation Blending Mode 0 0 Alt+Shift+T false Select Color Blending Mode Select Color Blending Mode Select Color Blending Mode 0 0 Alt+Shift+C false Select Luminosity Blending Mode Select Luminosity Blending Mode Select Luminosity Blending Mode 0 0 Alt+Shift+Y false Animation Previous frame Move to previous frame Move to previous frame 1 0 false Next frame Move to next frame Move to next frame 1 0 false Play / pause animation Play / pause animation Play / pause animation 1 0 false addblankframe Create Blank Frame Add blank frame Add blank frame 100000 0 false addduplicateframe Create Duplicate Frame Add duplicate frame Add duplicate frame 100000 0 false Toggle onion skin Toggle onion skin Toggle onion skin 100000 0 false Previous Keyframe false Next Keyframe false First Frame false Last Frame false Auto Frame Mode true true Show in Timeline true Insert Keyframe Left Insert keyframes to the left of selection, moving the tail of animation to the right. 100000 0 false Insert Keyframe Right Insert keyframes to the right of selection, moving the tail of animation to the right. 100000 0 false Insert Multiple Keyframes Insert several keyframes based on user parameters. 100000 0 false Remove Frame and Pull Remove keyframes moving the tail of animation to the left 100000 0 false deletekeyframe Remove Keyframe Remove keyframes without moving anything around 100000 0 false Insert Column Left Insert column to the left of selection, moving the tail of animation to the right 100000 0 false Insert Column Right Insert column to the right of selection, moving the tail of animation to the right 100000 0 false Insert Multiple Columns Insert several columns based on user parameters. 100000 0 false Remove Column and Pull Remove columns moving the tail of animation to the left 100000 0 false Remove Column Remove columns without moving anything around 100000 0 false Insert Hold Frame Insert a hold frame after every keyframe 100000 0 false Insert Multiple Hold Frames Insert N hold frames after every keyframe 100000 0 false Remove Hold Frame Remove a hold frame after every keyframe 100000 0 false Remove Multiple Hold Frames Remove N hold frames after every keyframe 100000 0 false Insert Hold Column Insert a hold column into the frame at the current position 100000 0 false Insert Multiple Hold Columns Insert N hold columns into the frame at the current position 100000 0 false Remove Hold Column Remove a hold column from the frame at the current position 100000 0 false Remove Multiple Hold Columns Remove N hold columns from the frame at the current position 100000 0 false Mirror Frames Mirror frames' position 100000 0 false Mirror Columns Mirror columns' position 100000 0 false Copy to Clipboard Copy frames to clipboard 100000 0 false Cut to Clipboard Cut frames to clipboard 100000 0 false Paste from Clipboard Paste frames from clipboard 100000 0 false Copy Columns to Clipboard Copy columns to clipboard 100000 0 false Cut Columns to Clipboard Cut columns to clipboard 100000 0 false Paste Columns from Clipboard Paste columns from clipboard 100000 0 false Set Start Time 100000 0 false Set End Time 100000 0 false Update Playback Range 100000 0 false Layers Activate next layer Activate next layer Activate next layer 1000 0 PgUp false Activate previous layer Activate previous layer Activate previous layer 1000 0 PgDown false Activate previously selected layer Activate previously selected layer Activate previously selected layer 1000 0 ; false groupLayer &Group Layer Group Layer Group Layer 1000 0 false cloneLayer &Clone Layer Clone Layer Clone Layer 1000 0 false vectorLayer &Vector Layer Vector Layer Vector Layer 1000 0 false filterLayer &Filter Layer... Filter Layer Filter Layer 1000 0 false fillLayer &Fill Layer... Fill Layer Fill Layer 1000 0 false fileLayer &File Layer... File Layer File Layer 1000 0 false transparencyMask &Transparency Mask Transparency Mask Transparency Mask 100000 0 false filterMask &Filter Mask... Filter Mask Filter Mask 100000 0 false filterMask &Colorize Mask Colorize Mask Colorize Mask 100000 0 false transformMask &Transform Mask... Transform Mask Transform Mask 100000 0 false selectionMask &Local Selection Local Selection Local Selection 100000 0 false view-filter &Isolate Layer Isolate Layer Isolate Layer 1000 0 true layer-locked &Toggle layer lock Toggle layer lock Toggle layer lock 1000 0 false visible Toggle layer &visibility Toggle layer visibility Toggle layer visibility 1000 0 false transparency-locked Toggle layer &alpha Toggle layer alpha Toggle layer alpha 1000 0 false transparency-enabled Toggle layer alpha &inheritance Toggle layer alpha inheritance Toggle layer alpha inheritance 1000 0 false paintLayer &Paint Layer Paint Layer Paint Layer 1000 0 Insert false &New Layer From Visible New layer from visible New layer from visible 1000 0 false duplicatelayer &Duplicate Layer or Mask Duplicate Layer or Mask Duplicate Layer or Mask 1000 0 Ctrl+J false &Cut Selection to New Layer Cut Selection to New Layer Cut Selection to New Layer 100000000 1 Ctrl+Shift+J false Copy &Selection to New Layer Copy Selection to New Layer Copy Selection to New Layer 100000000 0 Ctrl+Alt+J false Copy Layer Copy layer to clipboard Copy layer to clipboard 1000 0 false Cut Layer Cut layer to clipboard Cut layer to clipboard 1000 0 false Paste Layer Paste layer from clipboard Paste layer from clipboard 1000 0 false Quick Group Create a group layer containing selected layers Quick Group 1000 0 Ctrl+G false Quick Ungroup Remove grouping of the layers or remove one layer out of the group Quick Ungroup 100000 0 Ctrl+Alt+G false Quick Clipping Group Group selected layers and add a layer with clipped alpha channel Quick Clipping Group 100000 0 Ctrl+Shift+G false All Layers Select all layers Select all layers 10000 0 false Visible Layers Select all visible layers Select all visible layers 10000 0 false Locked Layers Select all locked layers Select all locked layers 10000 0 false Invisible Layers Select all invisible layers Select all invisible layers 10000 0 false Unlocked Layers Select all unlocked layers Select all unlocked layers 10000 0 false document-save &Save Layer/Mask... Save Layer/Mask Save Layer/Mask 1000 0 false document-save Save Vector Layer as SVG... Save Vector Layer as SVG Save Vector Layer as SVG 1000 0 false document-save Save &Group Layers... Save Group Layers Save Group Layers 100000 0 false Convert group to &animated layer Convert child layers into animation frames Convert child layers into animation frames 100000 0 false Convert to &animated layer Convert layer into animation frames Convert layer into animation frames 100000 0 false fileLayer to &File Layer Saves out the layers into a new image and then references that image. Convert to File Layer 100000 0 false I&mport Layer... Import Layer Import Layer 100000 0 false paintLayer &as Paint Layer... as Paint Layer as Paint Layer 1000 0 false transparencyMask as &Transparency Mask... as Transparency Mask as Transparency Mask 1000 0 false filterMask as &Filter Mask... as Filter Mask as Filter Mask 1000 0 false selectionMask as &Selection Mask... as Selection Mask as Selection Mask 1000 0 false paintLayer to &Paint Layer to Paint Layer to Paint Layer 1000 0 false transparencyMask to &Transparency Mask to Transparency Mask to Transparency Mask 1000 0 false filterMask to &Filter Mask... to Filter Mask to Filter Mask 1000 0 false selectionMask to &Selection Mask to Selection Mask to Selection Mask 1000 0 false transparencyMask &Alpha into Mask Alpha into Mask Alpha into Mask 100000 10 false transparency-enabled &Write as Alpha Write as Alpha Write as Alpha 1000000 1 false document-save &Save Merged... Save Merged Save Merged 1000000 0 false split-layer Split Layer... Split Layer Split Layer 1000 0 false Wavelet Decompose ... Wavelet Decompose Wavelet Decompose 1000 1 false symmetry-horizontal Mirror Layer Hori&zontally Mirror Layer Horizontally Mirror Layer Horizontally 1000 1 false symmetry-vertical Mirror Layer &Vertically Mirror Layer Vertically Mirror Layer Vertically 1000 1 false &Rotate Layer... Rotate Layer Rotate Layer 1000 1 false object-rotate-right Rotate &Layer 90° to the Right Rotate Layer 90° to the Right Rotate Layer 90° to the Right 1000 1 false object-rotate-left Rotate Layer &90° to the Left Rotate Layer 90° to the Left Rotate Layer 90° to the Left 1000 1 false Rotate Layer &180° Rotate Layer 180° Rotate Layer 180° 1000 1 false Scale &Layer to new Size... Scale Layer to new Size Scale Layer to new Size 100000 1 false &Shear Layer... Shear Layer Shear Layer 1000 1 false symmetry-horizontal Mirror All Layers Hori&zontally Mirror All Layers Horizontally Mirror All Layers Horizontally 1000 1 false symmetry-vertical Mirror All Layers &Vertically Mirror All Layers Vertically Mirror All Layers Vertically 1000 1 false &Rotate All Layers... Rotate All Layers Rotate All Layers 1000 1 false object-rotate-right Rotate All &Layers 90° to the Right Rotate All Layers 90° to the Right Rotate All Layers 90° to the Right 1000 1 false object-rotate-left Rotate All Layers &90° to the Left Rotate All Layers 90° to the Left Rotate All Layers 90° to the Left 1000 1 false Rotate All Layers &180° Rotate All Layers 180° Rotate All Layers 180° 1000 1 false Scale All &Layers to new Size... Scale All Layers to new Size Scale All Layers to new Size 100000 1 false &Shear All Layers... Shear All Layers Shear All Layers 1000 1 false &Offset Layer... Offset Layer Offset Layer 100000 1 false Clones &Array... Clones Array Clones Array 100000 0 false &Edit metadata... Edit metadata Edit metadata 100000 1 false &Histogram... Histogram Histogram 100000 0 false &Convert Layer Color Space... Convert Layer Color Space Convert Layer Color Space 100000 1 false merge-layer-below &Merge with Layer Below Merge with Layer Below Merge with Layer Below 100000 0 Ctrl+E false &Flatten Layer Flatten Layer Flatten Layer 100000 0 false Ras&terize Layer Rasterize Layer Rasterize Layer 10000000 1 false Flatten ima&ge Flatten image Flatten image 100000 0 Ctrl+Shift+E false La&yer Style... Layer Style Layer Style 100000 1 false Move into previous group Move into previous group Move into previous group 0 0 false Move into next group Move into next group Move into next group 0 0 false Rename current layer Rename current layer Rename current layer 100000 0 F2 false deletelayer &Remove Layer Remove Layer Remove Layer 1000 1 Shift+Delete false arrowupblr Move Layer or Mask Up Move Layer or Mask Up Ctrl+PgUp false arrowdown Move Layer or Mask Down Move Layer or Mask Down Ctrl+PgDown false properties &Properties... Properties Properties 1000 1 F3 false Set Copy F&rom... Set the source for the selected clone layer(s). Set Copy From 1000 1 false diff --git a/krita/krita4.xmlgui b/krita/krita4.xmlgui index b79131225a..531eee5e33 100644 --- a/krita/krita4.xmlgui +++ b/krita/krita4.xmlgui @@ -1,408 +1,409 @@ - + &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/main.cc b/krita/main.cc index a75c3a8f34..1523923528 100644 --- a/krita/main.cc +++ b/krita/main.cc @@ -1,596 +1,598 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2002 Patrick Julien * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "data/splash/splash_screen.xpm" #include "data/splash/splash_holidays.xpm" #include "data/splash/splash_screen_x2.xpm" #include "data/splash/splash_holidays_x2.xpm" #include "KisDocument.h" #include "kis_splash_screen.h" #include "KisPart.h" #include "KisApplicationArguments.h" #include #include "input/KisQtWidgetsTweaker.h" #include #include #ifdef Q_OS_ANDROID #include #endif #if defined Q_OS_WIN #include "config_use_qt_tablet_windows.h" #include #ifndef USE_QT_TABLET_WINDOWS #include #include #else #include #endif #include "config-high-dpi-scale-factor-rounding-policy.h" #include "config-set-has-border-in-full-screen-default.h" #ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT #include #endif #include #endif #if defined HAVE_KCRASH #include #elif defined USE_DRMINGW namespace { void tryInitDrMingw() { wchar_t path[MAX_PATH]; QString pathStr = QCoreApplication::applicationDirPath().replace(L'/', L'\\') + QStringLiteral("\\exchndl.dll"); if (pathStr.size() > MAX_PATH - 1) { return; } int pathLen = pathStr.toWCharArray(path); path[pathLen] = L'\0'; // toWCharArray doesn't add NULL terminator HMODULE hMod = LoadLibraryW(path); if (!hMod) { return; } // No need to call ExcHndlInit since the crash handler is installed on DllMain auto myExcHndlSetLogFileNameA = reinterpret_cast(GetProcAddress(hMod, "ExcHndlSetLogFileNameA")); if (!myExcHndlSetLogFileNameA) { return; } // Set the log file path to %LocalAppData%\kritacrash.log QString logFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation).replace(L'/', L'\\') + QStringLiteral("\\kritacrash.log"); myExcHndlSetLogFileNameA(logFile.toLocal8Bit()); } } // namespace #endif #ifdef Q_OS_WIN namespace { typedef enum ORIENTATION_PREFERENCE { ORIENTATION_PREFERENCE_NONE = 0x0, ORIENTATION_PREFERENCE_LANDSCAPE = 0x1, ORIENTATION_PREFERENCE_PORTRAIT = 0x2, ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED = 0x4, ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED = 0x8 } ORIENTATION_PREFERENCE; typedef BOOL WINAPI (*pSetDisplayAutoRotationPreferences_t)( ORIENTATION_PREFERENCE orientation ); void resetRotation() { QLibrary user32Lib("user32"); if (!user32Lib.load()) { qWarning() << "Failed to load user32.dll! This really should not happen."; return; } pSetDisplayAutoRotationPreferences_t pSetDisplayAutoRotationPreferences = reinterpret_cast(user32Lib.resolve("SetDisplayAutoRotationPreferences")); if (!pSetDisplayAutoRotationPreferences) { dbgKrita << "Failed to load function SetDisplayAutoRotationPreferences"; return; } bool result = pSetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE); dbgKrita << "SetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE) returned" << result; } } // namespace #endif #ifdef Q_OS_ANDROID extern "C" JNIEXPORT void JNICALL Java_org_krita_android_JNIWrappers_saveState(JNIEnv* /*env*/, jobject /*obj*/, jint /*n*/) { if (!KisPart::exists()) return; KisPart *kisPart = KisPart::instance(); QList> list = kisPart->documents(); for (QPointer &doc: list) { doc->autoSaveOnPause(); } const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", "OPENGL_SUCCESS"); } extern "C" JNIEXPORT void JNICALL Java_org_krita_android_JNIWrappers_exitFullScreen(JNIEnv* /*env*/, jobject /*obj*/, jint /*n*/) { if (!KisPart::exists()) return; KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); mainWindow->viewFullscreen(false); } __attribute__ ((visibility ("default"))) #endif extern "C" int main(int argc, char **argv) { // The global initialization of the random generator qsrand(time(0)); bool runningInKDE = !qgetenv("KDE_FULL_SESSION").isEmpty(); #if defined HAVE_X11 qputenv("QT_QPA_PLATFORM", "xcb"); #endif // Workaround a bug in QNetworkManager qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1)); // A per-user unique string, without /, because QLocalServer cannot use names with a / in it QString key = "Krita4" + QStandardPaths::writableLocation(QStandardPaths::HomeLocation).replace("/", "_"); key = key.replace(":", "_").replace("\\","_"); QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); QCoreApplication::setAttribute(Qt::AA_DisableShaderDiskCache, true); #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY // This rounding policy depends on a series of patches to Qt related to // https://bugreports.qt.io/browse/QTBUG-53022. These patches are applied // in ext_qt for WIndows (patches 0031-0036). // // The rounding policy can be set externally by setting the environment // variable `QT_SCALE_FACTOR_ROUNDING_POLICY` to one of the following: // Round: Round up for .5 and above. // Ceil: Always round up. // Floor: Always round down. // RoundPreferFloor: Round up for .75 and above. // PassThrough: Don't round. // // The default is set to RoundPreferFloor for better behaviour than before, // but can be overridden by the above environment variable. QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor); #endif #ifdef Q_OS_ANDROID const QString write_permission = "android.permission.WRITE_EXTERNAL_STORAGE"; const QStringList permissions = { write_permission }; const QtAndroid::PermissionResultMap resultHash = QtAndroid::requestPermissionsSync(QStringList(permissions)); if (resultHash[write_permission] == QtAndroid::PermissionResult::Denied) { // TODO: show a dialog and graciously exit dbgKrita << "Permission denied by the user"; } else { dbgKrita << "Permission granted"; } #endif const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); bool singleApplication = true; bool enableOpenGLDebug = false; bool openGLDebugSynchronous = false; bool logUsage = true; { singleApplication = kritarc.value("EnableSingleApplication", true).toBool(); if (kritarc.value("EnableHiDPI", true).toBool()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } if (!qgetenv("KRITA_HIDPI").isEmpty()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY if (kritarc.value("EnableHiDPIFractionalScaling", true).toBool()) { QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); } #endif if (!qgetenv("KRITA_OPENGL_DEBUG").isEmpty()) { enableOpenGLDebug = true; } else { enableOpenGLDebug = kritarc.value("EnableOpenGLDebug", false).toBool(); } if (enableOpenGLDebug && (qgetenv("KRITA_OPENGL_DEBUG") == "sync" || kritarc.value("OpenGLDebugSynchronous", false).toBool())) { openGLDebugSynchronous = true; } KisConfig::RootSurfaceFormat rootSurfaceFormat = KisConfig::rootSurfaceFormat(&kritarc); KisOpenGL::OpenGLRenderer preferredRenderer = KisOpenGL::RendererAuto; logUsage = kritarc.value("LogUsage", true).toBool(); #ifdef Q_OS_WIN const QString preferredRendererString = kritarc.value("OpenGLRenderer", "angle").toString(); #else const QString preferredRendererString = kritarc.value("OpenGLRenderer", "auto").toString(); #endif preferredRenderer = KisOpenGL::convertConfigToOpenGLRenderer(preferredRendererString); const KisOpenGL::RendererConfig config = KisOpenGL::selectSurfaceConfig(preferredRenderer, rootSurfaceFormat, enableOpenGLDebug); KisOpenGL::setDefaultSurfaceConfig(config); KisOpenGL::setDebugSynchronous(openGLDebugSynchronous); #ifdef Q_OS_WIN // HACK: https://bugs.kde.org/show_bug.cgi?id=390651 resetRotation(); #endif } if (logUsage) { KisUsageLogger::initialize(); } QString root; QString language; { // Create a temporary application to get the root QCoreApplication app(argc, argv); Q_UNUSED(app); root = KoResourcePaths::getApplicationRoot(); QSettings languageoverride(configPath + QStringLiteral("/klanguageoverridesrc"), QSettings::IniFormat); languageoverride.beginGroup(QStringLiteral("Language")); language = languageoverride.value(qAppName(), "").toString(); } #ifdef Q_OS_LINUX { QByteArray originalXdgDataDirs = qgetenv("XDG_DATA_DIRS"); if (originalXdgDataDirs.isEmpty()) { // We don't want to completely override the default originalXdgDataDirs = "/usr/local/share/:/usr/share/"; } qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share") + ":" + originalXdgDataDirs); } #else qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share")); #endif dbgKrita << "Setting XDG_DATA_DIRS" << qgetenv("XDG_DATA_DIRS"); // Now that the paths are set, set the language. First check the override from the language // selection dialog. dbgKrita << "Override language:" << language; bool rightToLeft = false; if (!language.isEmpty()) { KLocalizedString::setLanguages(language.split(":")); // And override Qt's locale, too qputenv("LANG", language.split(":").first().toLocal8Bit()); QLocale locale(language.split(":").first()); QLocale::setDefault(locale); const QStringList rtlLanguages = QStringList() << "ar" << "dv" << "he" << "ha" << "ku" << "fa" << "ps" << "ur" << "yi"; if (rtlLanguages.contains(language.split(':').first())) { rightToLeft = true; } } else { dbgKrita << "Qt UI languages:" << QLocale::system().uiLanguages() << qgetenv("LANG"); // And if there isn't one, check the one set by the system. QLocale locale = QLocale::system(); if (locale.name() != QStringLiteral("en")) { QStringList uiLanguages = locale.uiLanguages(); for (QString &uiLanguage : uiLanguages) { // This list of language codes that can have a specifier should // be extended whenever we have translations that need it; right // now, only en, pt, zh are in this situation. if (uiLanguage.startsWith("en") || uiLanguage.startsWith("pt")) { uiLanguage.replace(QChar('-'), QChar('_')); } else if (uiLanguage.startsWith("zh-Hant") || uiLanguage.startsWith("zh-TW")) { uiLanguage = "zh_TW"; } else if (uiLanguage.startsWith("zh-Hans") || uiLanguage.startsWith("zh-CN")) { uiLanguage = "zh_CN"; } } for (int i = 0; i < uiLanguages.size(); i++) { QString uiLanguage = uiLanguages[i]; // Strip the country code int idx = uiLanguage.indexOf(QChar('-')); if (idx != -1) { uiLanguage = uiLanguage.left(idx); uiLanguages.replace(i, uiLanguage); } } dbgKrita << "Converted ui languages:" << uiLanguages; qputenv("LANG", uiLanguages.first().toLocal8Bit()); #ifdef Q_OS_MAC // See https://bugs.kde.org/show_bug.cgi?id=396370 KLocalizedString::setLanguages(QStringList() << uiLanguages.first()); #else KLocalizedString::setLanguages(QStringList() << uiLanguages); #endif } } #if defined Q_OS_WIN && defined USE_QT_TABLET_WINDOWS && defined QT_HAS_WINTAB_SWITCH const bool forceWinTab = !KisConfig::useWin8PointerInputNoApp(&kritarc); QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI, forceWinTab); if (qEnvironmentVariableIsEmpty("QT_WINTAB_DESKTOP_RECT") && qEnvironmentVariableIsEmpty("QT_IGNORE_WINTAB_MAPPING")) { QRect customTabletRect; KisDlgCustomTabletResolution::Mode tabletMode = KisDlgCustomTabletResolution::getTabletMode(&customTabletRect); KisDlgCustomTabletResolution::applyConfiguration(tabletMode, customTabletRect); } #endif // first create the application so we can create a pixmap KisApplication app(key, argc, argv); KisUsageLogger::writeHeader(); KisOpenGL::initialize(); #ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT if (QCoreApplication::testAttribute(Qt::AA_UseDesktopOpenGL)) { QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true); } #endif if (!language.isEmpty()) { if (rightToLeft) { app.setLayoutDirection(Qt::RightToLeft); } else { app.setLayoutDirection(Qt::LeftToRight); } } KLocalizedString::setApplicationDomain("krita"); dbgKrita << "Available translations" << KLocalizedString::availableApplicationTranslations(); dbgKrita << "Available domain translations" << KLocalizedString::availableDomainTranslations("krita"); #ifdef Q_OS_WIN QDir appdir(KoResourcePaths::getApplicationRoot()); QString path = qgetenv("PATH"); qputenv("PATH", QFile::encodeName(appdir.absolutePath() + "/bin" + ";" + appdir.absolutePath() + "/lib" + ";" + appdir.absolutePath() + "/Frameworks" + ";" + appdir.absolutePath() + ";" + path)); dbgKrita << "PATH" << qgetenv("PATH"); #endif if (qApp->applicationDirPath().contains(KRITA_BUILD_DIR)) { qFatal("FATAL: You're trying to run krita from the build location. You can only run Krita from the installation location."); } #if defined HAVE_KCRASH KCrash::initialize(); #elif defined USE_DRMINGW tryInitDrMingw(); #endif // If we should clear the config, it has to be done as soon as possible after // KisApplication has been created. Otherwise the config file may have been read // and stored in a KConfig object we have no control over. app.askClearConfig(); KisApplicationArguments args(app); if (singleApplication && app.isRunning()) { // only pass arguments to main instance if they are not for batch processing // any batch processing would be done in this separate instance const bool batchRun = args.exportAs() || args.exportSequence(); if (!batchRun) { QByteArray ba = args.serialize(); if (app.sendMessage(ba)) { return 0; } } } if (!runningInKDE) { // Icons in menus are ugly and distracting app.setAttribute(Qt::AA_DontShowIconsInMenus); } - +#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) + app.setAttribute(Qt::AA_DisableWindowContextHelpButton); +#endif app.installEventFilter(KisQtWidgetsTweaker::instance()); if (!args.noSplash()) { // then create the pixmap from an xpm: we cannot get the // location of our datadir before we've started our components, // so use an xpm. QDate currentDate = QDate::currentDate(); QWidget *splash = 0; if (currentDate > QDate(currentDate.year(), 12, 4) || currentDate < QDate(currentDate.year(), 1, 9)) { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_holidays_xpm), QPixmap(splash_holidays_x2_xpm)); } else { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_screen_xpm), QPixmap(splash_screen_x2_xpm)); } app.setSplashScreen(splash); } #if defined Q_OS_WIN KisConfig cfg(false); bool supportedWindowsVersion = true; QOperatingSystemVersion osVersion = QOperatingSystemVersion::current(); if (osVersion.type() == QOperatingSystemVersion::Windows) { if (osVersion.majorVersion() >= QOperatingSystemVersion::Windows7.majorVersion()) { supportedWindowsVersion = true; } else { supportedWindowsVersion = false; if (cfg.readEntry("WarnedAboutUnsupportedWindows", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running an unsupported version of Windows: %1.\n" "This is not recommended. Do not report any bugs.\n" "Please update to a supported version of Windows: Windows 7, 8, 8.1 or 10.", osVersion.name())); cfg.writeEntry("WarnedAboutUnsupportedWindows", true); } } } #ifndef USE_QT_TABLET_WINDOWS { if (cfg.useWin8PointerInput() && !KisTabletSupportWin8::isAvailable()) { cfg.setUseWin8PointerInput(false); } if (!cfg.useWin8PointerInput()) { bool hasWinTab = KisTabletSupportWin::init(); if (!hasWinTab && supportedWindowsVersion) { if (KisTabletSupportWin8::isPenDeviceAvailable()) { // Use WinInk automatically cfg.setUseWin8PointerInput(true); } else if (!cfg.readEntry("WarnedAboutMissingWinTab", false)) { if (KisTabletSupportWin8::isAvailable()) { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver and no Windows Ink pen devices are found. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } else { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } cfg.writeEntry("WarnedAboutMissingWinTab", true); } } } if (cfg.useWin8PointerInput()) { KisTabletSupportWin8 *penFilter = new KisTabletSupportWin8(); if (penFilter->init()) { // penFilter.registerPointerDeviceNotifications(); app.installNativeEventFilter(penFilter); dbgKrita << "Using Win8 Pointer Input for tablet support"; } else { dbgKrita << "No Win8 Pointer Input available"; delete penFilter; } } } #elif defined QT_HAS_WINTAB_SWITCH // Check if WinTab/WinInk has actually activated const bool useWinTabAPI = app.testAttribute(Qt::AA_MSWindowsUseWinTabAPI); if (useWinTabAPI != !cfg.useWin8PointerInput()) { cfg.setUseWin8PointerInput(useWinTabAPI); } #endif #endif app.setAttribute(Qt::AA_CompressHighFrequencyEvents, false); // Set up remote arguments. QObject::connect(&app, SIGNAL(messageReceived(QByteArray,QObject*)), &app, SLOT(remoteArguments(QByteArray,QObject*))); QObject::connect(&app, SIGNAL(fileOpenRequest(QString)), &app, SLOT(fileOpenRequested(QString))); // Hardware information - KisUsageLogger::write("\nHardware Information\n"); - KisUsageLogger::write(QString(" GPU Acceleration: %1").arg(kritarc.value("OpenGLRenderer", "auto").toString())); - KisUsageLogger::write(QString(" Memory: %1 Mb").arg(KisImageConfig(true).totalRAM())); - KisUsageLogger::write(QString(" Number of Cores: %1").arg(QThread::idealThreadCount())); - KisUsageLogger::write(QString(" Swap Location: %1\n").arg(KisImageConfig(true).swapDir())); + 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 c549db0adc..3535cf4b23 100644 --- a/krita/org.kde.krita.appdata.xml +++ b/krita/org.kde.krita.appdata.xml @@ -1,381 +1,389 @@ 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 Fundación Krita Asas Krita Fondazione Krita Krita Foundation Krita Foundation Krita Foundation Fundacja Krity Fundação do Krita Krita Foundation Nadácia Krita Krita-stiftelsen Krita Vakfı Фундація Krita xxKrita Foundationxx Krita 基金会 Krita 基金會 foundation@krita.org Krita كريتا Krita Krita Krita Krita Krita Krita Krita + Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita xxKritaxx Krita Krita Digital Painting, Creative Freedom رسم رقميّ، حريّة إبداعيّة Digitalno crtanje, kreativna sloboda Dibuix digital, Llibertat creativa Dibuix digital, Llibertat creativa Digitální malování, svoboda tvorby Digital tegning, kunstnerisk frihed Digitales Malen, kreative Freiheit Ψηφιακή ζωγραφική, δημιουργική ελευθερία Digital Painting, Creative Freedom Pintura digital, libertad creativa Digitaalne joonistamine, loominguline vabadus Margolan digitala, sormen askatasuna Digitaalimaalaus, luova vapaus Peinture numérique, liberté créatrice Debuxo dixital, liberdade creativa Pictura digital, Libertate creative Pelukisan Digital, Kebebasan Berkreatif Pittura digitale, libertà creativa 디지털 페인팅, 자유로운 창의성 Digital Painting, Creative Freedom Digital teikning – kreativ fridom Cyfrowe malowanie, Wolność Twórcza Pintura Digital, Liberdade Criativa Pintura digital, liberdade criativa Цифровое рисование. Творческая свобода Digitálne maľovanie, kreatívna sloboda Digital målning, kreativ frihet Sayısal Boyama, Yaratıcı Özgürlük Цифрове малювання, творча свобода xxDigital Painting, Creative Freedomxx 自由挥洒数字绘画的无限创意 數位繪畫,創作自由

Krita is the full-featured digital art studio.

Krita je potpuni digitalni umjetnički studio.

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

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

Krita ist ein digitales Designstudio mit umfangreichen Funktionen.

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

Krita is the full-featured digital art studio.

Krita es un estudio de arte digital completo

Krita on rohkete võimalustega digitaalkunstistuudio.

Krita arte lantegi digital osoa da.

Krita on täyspiirteinen digitaiteen ateljee.

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

Krita é un estudio completo de arte dixital.

Krita es le studio de arte digital complete.

Krita adalah studio seni digital yang penuh dengan fitur.

Krita è uno studio d'arte digitale completo.

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

Krita는 디지털 예술 스튜디오입니다.

Krita is de digitale kunststudio vol mogelijkheden.

Krita er ei funksjonsrik digital teiknestove.

Krita jest pełnowymiarowym, cyfrowym studiem artystycznym

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

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

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

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

Krita är den fullfjädrade digitala konststudion.

Krita, tam özellikli dijital sanat stüdyosudur.

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

xxKrita is the full-featured digital art studio.xx

Krita 是一款功能齐全的数字绘画工作室软件。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

스케치, 페인팅을 위한 완벽한 도구이며, 생각에서부터 디지털 페인팅 파일을 만들어 낼 수 있는 종합적인 도구를 제공합니다.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

https://www.krita.org/ https://docs.krita.org/KritaFAQ.html https://krita.org/support-us/donations/ https://docs.krita.org/ https://docs.krita.org/en/untranslatable_pages/reporting_bugs.html Krita is a full-featured digital painting studio. Krita és un estudi de pintura digital ple de funcionalitats. Krita és un estudi de pintura digital ple de funcionalitats. Krita ist ein digitales Zeichenstudio mit umfangreichen Funktionen. Krita is a full-featured digital painting studio. Krita es un completo estudio de dibujo digital. + Krita on rohkete võimalustega digitaalkunstistuudio. Krita é un estudio completo de debuxo dixital. Krita adalah studio pelukisan digital dengan fitur yang lengkap. Krita è uno studio d'arte digitale completo. Krita는 다기능 디지털 예술 스튜디오입니다. Krita is een digitale schilderstudio vol mogelijkheden. Krita er ei funksjonsrik digital teiknestove. Krita jest pełnowymiarowym, cyfrowym studiem artystycznym. O Krita é um estúdio de arte digital completo. O Krita é um estúdio de pintura digital completo. Krita je plnohodnotné digitálne maliarske štúdio. Krita är en fullfjädrad digital konststudio. Krita — повноцінний комплекс для цифрового малювання. xxKrita is a full-featured digital painting studio.xx Krita 是一款功能齐全的数字绘画工作室软件。 Krita 是全功能的數位繪圖工作室。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_001.png The startup window now also gives you the latest news about Krita. La finestra d'inici ara proporciona les darreres notícies quant al Krita. La finestra d'inici ara proporciona les darreres noticies quant al Krita. The startup window now also gives you the latest news about Krita. La ventana de bienvenida también le proporciona ahora las últimas noticias sobre Krita. + Käivitusaken jagab nüüd ka Krita värskemaid uudiseid. Agora a xanela de inicio tamén fornece as últimas novas sobre Krita. Window pemulaian kini juga memberikan kamu kabar terkini tentang Krita. La finestra di avvio ora fornisce anche le ultime novità su Krita. 시작 창에서 Krita의 최신 소식을 볼 수 있습니다. Het opstartvenster geeft u nu ook you het laatste nieuws over Krita. Oppstartsvindauget viser no siste nytt om Krita. Okno początkowe teraz wyświetla wieści o Kricie. A janela inicial agora também lhe dá as últimas notícias sobre o Krita. A janela de inicialização agora também mostra as últimas notícias sobre o Krita. V úvodnom okne sa tiež nachádzajú najnovšie správy o Krita. Startfönstret ger nu också senaste nytt om Krita. У початковому вікні програми ви можете бачити найсвіжіші новини щодо Krita. xxThe startup window now also gives you the latest news about Krita.xx 启动画面现在可以为你呈现与 Krita 有关的最新资讯。 開始視窗也提供給您關於 Krita 的最新消息。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_002.png There are over ten immensely powerful brush engines. Hi ha uns deu motors de pinzell immensament potents. Hi ha uns deu motors de pinzell immensament potents. There are over ten immensely powerful brush engines. Existen unos diez inmensamente potentes motores de pinceles. + Üle kümne ääretult võimeka pintslimootori. Hai máis de dez motores de pinceis inmensamente potentes. Ada lebih dari sepuluh mesin kuas yang sangat manjur. Ci sono oltre dieci motori di pennelli incredibilmente potenti. 10가지 종류의 강력한 브러시 엔진을 사용할 수 있습니다. Er zijn meer dan tien immens krachtige penseelengines. Det finst meir enn ti enormt kraftige penselmotorar. Istnieje ponad dziesięć zaawansowanych silników pędzli. Existem mais de dez motores de pincéis extremamente poderosos. Mais de dez engines de pincéis incrivelmente poderosos disponíveis. Existuje viac ako desať nesmierne výkonných štetcových enginov. Det finns mer än tio enormt kraftfulla penselgränssnitt. У програмі передбачено понад десяток надзвичайно потужних рушіїв пензлів. xxThere are over ten immensely powerful brush engines.xx 它具备超过十种相当强大的笔刷引擎。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_003.png Create and use gamut masks to give your images a coherent feel. Creeu i useu màscares de gamma per donar a les imatges una aparença coherent. Creeu i useu màscares de gamma per donar a les imatges una aparença coherent. Create and use gamut masks to give your images a coherent feel. Cree y use gamas para proporcionar a sus imágenes un aspecto coherente. + Värviulatuse maskide loomine ja kasutamine piltidele kooskõlalise välimuse andmiseks. Crea e usa máscaras de gama para dar ás túas imaxes un aspecto coherente. Ciptakan dan gunakan masker gamut untuk memberikan gambarmu sebuah suasana koheren. Crea e utilizza maschere gamut per dare alle tue immagini un aspetto coerente. 색역 마스크를 만들고 사용할 수 있습니다. Maak en gebruik gamut-maskers om uw afbeeldingen een coherent gevoel te geven. Bruk fargeområde-masker for å gje bileta eit heilsleg uttrykk. Stwórz i używaj masek gamut, aby nadać swoim obrazom spójny wygląd. Crie e use máscaras de gamute para dar às suas imagens uma aparência coerente. Crie e use máscaras de gama para dar um senso de coerência às suas imagens. Vytvorte a používajte gamutové masky, aby vašim obrázkom poskytli ucelený pocit. Att skapa och använda färgomfångsmasker ger bilder en sammanhängande känsla. Створюйте маски палітри і користуйтеся ними для надання вашим малюнкам однорідного вигляду. xxCreate and use gamut masks to give your images a coherent feel.xx 创建并使用色域蒙版可以为你的图像带来更加一致的观感。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_004.png Into animation? Krita provides everything you need for traditional, hand-drawn animation. Esteu amb animacions? El Krita proporciona tot el que cal per a l'animació a mà tradicional. Esteu amb animacions? El Krita proporciona tol el que cal per a l'animació a mà tradicional. Into animation? Krita provides everything you need for traditional, hand-drawn animation. ¿Trabaja con animación? Krita proporciona todo lo necesario para la animación manual tradicional. + Sind huvitab animatsioon? Krita pakub kõike, mida läheb tarvis traditsioonilise käsitsi loodud animatsiooni jaoks. Gusta das animacións? Krita fornece todo o necesario para animacións tradicionais debuxadas a man. Soal animasi? krita menyediakan apa pun yang kamu perlukan untuk animasi gambar-tangan, tradisional. Per le animazioni? Krita fornisce tutto ciò che ti server per l'animazione tradizionale, disegnata a mano. 애니메이션을 만들 계획이 있으신가요? Krita를 통해서 수작업 애니메이션을 작업할 수 있습니다. Naar animatie? Krita biedt alles wat u nodig hebt voor traditionele, met de hand getekende animatie. Interessert i animasjon? Krita har alt du treng for tradisjonelle, handteikna animasjonar. Zajmujesz się animacjami? Krita zapewnia wszystko czego potrzebujesz do tworzenia tradycyjnych, ręcznie rysowanych animacji. Gosta de animação? O Krita oferece tudo o que precisa para o desenho animado tradicional e desenhado à mão. Curte animação? O Krita fornece tudo necessário para você poder trabalhar com animação tradicional ou feita à mão. Ste do animácie? Krita poskytuje všetko, čo potrebujete pre tradičné ručne kreslené animácie. Gillar du animering? Krita tillhandahåller allt som behövs för traditionella, handritade animeringar. Працюєте із анімацією? У Krita ви знайдете усе, що потрібно для створення традиційної, намальованої вручну анімації. xxInto animation? Krita provides everything you need for traditional, hand-drawn animation.xx 喜欢制作动画吗?Krita 提供了制作传统手绘动画的全套工具。 想做動畫?Krita 提供您在傳統、手繪動畫所需的任何東西。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_005.png If you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual. Si sou nou a la pintura digital, o voleu conèixer més quant a les possibilitats del Krita, hi ha un ampli manual actualitzat. Si sou nou a la pintura digital, o voleu conèixer més quant a les possibilitats del Krita, hi ha un ampli manual actualitzat. If you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual. Si está empezando con el dibujo digital o si quiere saber más sobre la posibilidades de Krita, dispone de un extenso y actualizado manual. + Kui oled digitaalkunstis alles uustulnuk või tunned huvi Krita võimaluste vastu, on meil välja pakkuda mahukas ajakohane käsiraamat. Se está a empezar co debuxo dixital, ou quere saber máis sobre as posibilidades de Krita, existe un manual exhaustivo e actualizado. Jika kamu baru dalam pelukisan digital, atau ingin mengetahui selebihnya tentang Krita, di situ ada manual yang update dan luas. Se sei nuovo del disegno digitale, o vuoi saperne di più sulle possibilità di Krita, è disponibile un manuale completo e aggiornato. 디지털 페인팅을 처음 시작하시거나, Krita의 기능을 더 알아 보려면 사용 설명서를 참조하십시오. Als u nieuw bent in digitaal schilderen of u wilt meer weten over de mogelijkheden van Krita, dan is er een uitgebreide, bijgewerkte handleiding. Viss du er nybegynnar innan digital teikning, eller ønskjer å veta meir om kva som er mogleg med Krita, finst det ei omfattande og oppdatert brukarhandbok. Jeśli cyfrowe malowanie to dla ciebie nowość, lub jeśli chcesz dowiedzieć się więcej o możliwościach Krity, to dostępny jest wyczerpująca i aktualna instrukcja obsługi. Se é novo na pintura digital, ou deseja saber mais sobre as possibilidades do Krita, existe um manual extenso e actualizado. Se você for iniciante em pintura digital ou gostaria de saber mais sobre as possibilidades que o Krita oferece, há um extenso e atualizado manual para isso. Ak ste v oblasti digitálnej maľby nováčikom alebo sa chcete dozvedieť viac o možnostiach programu Krita, existuje o tom rozsiahla a aktuálna príručka. Om digital målning är nytt för dig, eller om du vill veta mer om Kritas möjligheter, finns en omfattande, aktuell handbok. Якщо ви не маєте достатнього досвіду у цифровому малюванні або хочете дізнатися більше про можливості Krita, скористайтеся нашим докладним і актуальним підручником. xxIf you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual.xx 不管你是数字绘画的新手,还是想发现 Krita 更多的用法,你都可以在我们详尽并持续更新的使用手册中找到答案。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_006.png Graphics 2DGraphics RasterGraphics KDE krita org.kde.krita.desktop https://www.microsoft.com/store/apps/9n6x57zgrw96
diff --git a/libs/command/kis_undo_store.h b/libs/command/kis_undo_store.h index dc57a06516..2a56a77686 100644 --- a/libs/command/kis_undo_store.h +++ b/libs/command/kis_undo_store.h @@ -1,81 +1,81 @@ /* * Copyright (c) 2003 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_UNDO_STORE_H_ #define KIS_UNDO_STORE_H_ #include #include #include class KUndo2Command; class KUndo2MagicString; /** - * See also: http://community.kde.org/Krita/Undo_adapter_vs_Undo_store + * See also: https://community.kde.org/Krita/Undo_adapter_vs_Undo_store * * Split the functionality of KisUndoAdapter into two classes: * KisUndoStore and KisUndoAdapter. The former one works as an * interface to an external storage of the undo information: * undo stack, KisDocument, /dev/null. The latter one defines the * behavior of the system when someone wants to add a command. There * are three variants: * 1) KisSurrogateUndoAdapter -- saves commands directly to the * internal stack. Used for wrapping around legacy code into * a single command. * 2) KisLegacyUndoAdapter -- blocks the strokes and updates queue, * and then adds the command to a store * 3) KisPostExecutionUndoAdapter -- used by the strokes. It doesn't * call redo() when you add a command. It is assumed, that you have * already executed the command yourself and now just notify * the system about it. Warning: it doesn't inherit KisUndoAdapter * because it doesn't fit the contract of this class. And, more * important, KisTransaction should work differently with this class. * * The ownership on the KisUndoStore (that substituted KisUndoAdapter * in the document's code) now belongs to the image. It means that * KisDocument::createUndoStore() is just a factory method, the document * doesn't store the undo store itself. */ class KRITACOMMAND_EXPORT KisUndoStore { public: KisUndoStore(); virtual ~KisUndoStore(); public: /** * WARNING: All these methods are not considered as thread-safe */ virtual const KUndo2Command* presentCommand() = 0; virtual void undoLastCommand() = 0; virtual void addCommand(KUndo2Command *cmd) = 0; virtual void beginMacro(const KUndo2MagicString& macroName) = 0; virtual void endMacro() = 0; virtual void purgeRedoState() = 0; private: Q_DISABLE_COPY(KisUndoStore) }; #endif // KIS_UNDO_STORE_H_ diff --git a/libs/command/kundo2magicstring.h b/libs/command/kundo2magicstring.h index a8a6ebe0b0..0d5d486e8b 100644 --- a/libs/command/kundo2magicstring.h +++ b/libs/command/kundo2magicstring.h @@ -1,318 +1,318 @@ /* * Copyright (c) 2014 Dmitry Kazakov * Copyright (c) 2014 Alexander Potashev * * 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 KUNDO2MAGICSTRING_H #define KUNDO2MAGICSTRING_H #include #include #include #include "kritacommand_export.h" /** * \class KUndo2MagicString is a special wrapper for a string that is * going to passed to a KUndo2Command and be later shown in the undo * history and undo action in menu. The strings like that must have * (qtundo-format) context to let translators know that they are * allowed to use magic split in them. * * Magic split is used in some languages to split the message in the * undo history docker (which is either verb or noun in + * href="https://en.wikipedia.org/wiki/Nominative_case">noun in * nominative) and the message in undo/redo actions (which is - * usually a noun + * usually a noun * in accusative). When the translator needs it he, splits two * translations with '\n' symbol and the magic string will recognize * it. * * \note KUndo2MagicString will never support concatenation operators, * because in many languages you cannot combine words without * knowing the proper case. */ class KRITACOMMAND_EXPORT KUndo2MagicString { public: /** * Construct an empty string. Note that you cannot create a * non-empy string without special functions, all the calls to which * are processed by xgettext. */ KUndo2MagicString(); /** * Fetch the main translated string. That is the one that goes to * undo history and resembles the action name in verb/nominative */ QString toString() const; /** * Fetch the secondary string which will go to the undo/redo * action. This is usually a noun in accusative. If the * translator didn't provide a secondary string, toString() and * toSecondaryString() return the same values. */ QString toSecondaryString() const; /** * \return true if the contained string is empty */ bool isEmpty() const; bool operator==(const KUndo2MagicString &rhs) const; bool operator!=(const KUndo2MagicString &rhs) const; private: /** * Construction of a magic string is allowed only with the means * of special macros which resemble their kde-wide counterparts */ explicit KUndo2MagicString(const QString &text); friend KUndo2MagicString kundo2_noi18n(const QString &text); template friend KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1); template friend KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1, const A2 &a2); template friend KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3); template friend KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4); friend KUndo2MagicString kundo2_i18n(const char *text); template friend KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1); template friend KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1, const A2 &a2); template friend KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3); template friend KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4); friend KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text); template friend KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text, const A1 &a1); template friend KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text, const A1 &a1, const A2 &a2); template friend KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text, const A1 &a1, const A2 &a2, const A3 &a3); template friend KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1); template friend KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1, const A2 &a2); template friend KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1, const A2 &a2, const A3 &a3); template friend KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1); template friend KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1, const A2 &a2); template friend KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1, const A2 &a2, const A3 &a3); private: QString m_text; }; inline QDebug operator<<(QDebug dbg, const KUndo2MagicString &v) { if (v.toString() != v.toSecondaryString()) { dbg.nospace() << v.toString() << "(" << v.toSecondaryString() << ")"; } else { dbg.nospace() << v.toString(); } return dbg.space(); } /** * This is a special wrapper to a string which tells explicitly * that we don't need a translation for a given string. It is used * either in testing or internal commands, which don't go to the * stack directly. */ inline KUndo2MagicString kundo2_noi18n(const QString &text) { return KUndo2MagicString(text); } template inline KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1) { return KUndo2MagicString(QString(text).arg(a1)); } template inline KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1, const A2 &a2) { return KUndo2MagicString(QString(text).arg(a1).arg(a2)); } template inline KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { return KUndo2MagicString(QString(text).arg(a1).arg(a2).arg(a3)); } template inline KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return KUndo2MagicString(QString(text).arg(a1).arg(a2).arg(a3).arg(a4)); } /** * Same as ki18n, but is supposed to work with strings going to * undo stack */ inline KUndo2MagicString kundo2_i18n(const char *text) { return KUndo2MagicString(i18nc("(qtundo-format)", text)); } template inline KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1) { return KUndo2MagicString(i18nc("(qtundo-format)", text, a1)); } template inline KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1, const A2 &a2) { return KUndo2MagicString(i18nc("(qtundo-format)", text, a1, a2)); } template inline KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { return KUndo2MagicString(i18nc("(qtundo-format)", text, a1, a2, a3)); } template inline KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return KUndo2MagicString(i18nc("(qtundo-format)", text, a1, a2, a3, a4)); } inline QString prependContext(const char *ctxt) { return QString("(qtundo-format) %1").arg(ctxt); } /** * Same as ki18nc, but is supposed to work with strings going to * undo stack */ inline KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text) { return KUndo2MagicString(i18nc(prependContext(ctxt).toLatin1().data(), text)); } template inline KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text, const A1 &a1) { return KUndo2MagicString(i18nc(prependContext(ctxt).toLatin1().data(), text, a1)); } template inline KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text, const A1 &a1, const A2 &a2) { return KUndo2MagicString(i18nc(prependContext(ctxt).toLatin1().data(), text, a1, a2)); } template inline KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { return KUndo2MagicString(i18nc(prependContext(ctxt).toLatin1().data(), text, a1, a2, a3)); } template inline KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return KUndo2MagicString(i18nc(prependContext(ctxt).toLatin1().data(), text, a1, a2, a3, a4)); } /** * Same as ki18np, but is supposed to work with strings going to * undo stack */ template inline KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1) { return KUndo2MagicString(i18ncp("(qtundo-format)", sing, plur, a1)); } template inline KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1, const A2 &a2) { return i18ncp("(qtundo-format)", sing, plur, a1, a2); } template inline KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1, const A2 &a2, const A3 &a3) { return i18ncp("(qtundo-format)", sing, plur, a1, a2, a3); } template inline KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return i18ncp("(qtundo-format)", sing, plur, a1, a2, a3, a4); } /** * Same as ki18ncp, but is supposed to work with strings going to * undo stack */ template inline KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1) { return KUndo2MagicString(i18ncp(prependContext(ctxt).toLatin1().data(), sing, plur, a1)); } template inline KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1, const A2 &a2) { return i18ncp(prependContext(ctxt).toLatin1().data(), sing, plur, a1, a2); } template inline KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1, const A2 &a2, const A3 &a3) { return i18ncp(prependContext(ctxt).toLatin1().data(), sing, plur, a1, a2, a3); } template inline KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return i18ncp(prependContext(ctxt).toLatin1().data(), sing, plur, a1, a2, a3, a4); } #endif /* KUNDO2MAGICSTRING_H */ diff --git a/libs/flake/KoCurveFit.h b/libs/flake/KoCurveFit.h index 35ab8b29a3..0f1ae931d8 100644 --- a/libs/flake/KoCurveFit.h +++ b/libs/flake/KoCurveFit.h @@ -1,49 +1,49 @@ /* This file is part of the KDE project Copyright (C) 2001-2003 Rob Buis Copyright (C) 2007 Jan Hambrecht This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCURVEFIT_H #define KOCURVEFIT_H #include #include #include "kritaflake_export.h" class KoPathShape; /* * Fits bezier curve to given list of points. * * An Algorithm for Automatically Fitting Digitized Curves * by Philip J. Schneider * from "Graphics Gems", Academic Press, 1990 * - * http://www.acm.org/pubs/tog/GraphicsGems/gems/FitCurves.c - * http://www.acm.org/pubs/tog/GraphicsGems/gems/README + * http://web.archive.org/web/20061118130015/http://www.acm.org/pubs/tog/GraphicsGems/gems/FitCurves.c + * http://web.archive.org/web/20040519052901/http://www.acm.org/pubs/tog/GraphicsGems/gems/README * * @param points the list of points to fit curve to * @param error the max. fitting error * @return a path shape representing the fitted curve */ KRITAFLAKE_EXPORT KoPathShape * bezierFit(const QList &points, float error); #endif diff --git a/libs/flake/KoOdfWorkaround.h b/libs/flake/KoOdfWorkaround.h index 038f4cf8c9..9bf9e91d95 100644 --- a/libs/flake/KoOdfWorkaround.h +++ b/libs/flake/KoOdfWorkaround.h @@ -1,162 +1,162 @@ /* This file is part of the KDE project Copyright (C) 2009 Thorsten Zachmann Copyright (C) 2011 Jan Hambrecht Copyright (C) 2011 Lukáš Tvrdý 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 KOODFWORKAROUND_H #define KOODFWORKAROUND_H #include "kritaflake_export.h" #include "KoTextShapeDataBase.h" #include #include #include class KoShape; class KoShapeLoadingContext; class QPen; class QColor; class QString; class KoColorBackground; /** * This class should contain all workarounds to correct problems with different ODF * implementations. If you need to access application specific things please create a * new namespace in the application you need it in * All calls to methods of this class should be wrapped into ifndefs like e.g. * * @code * #ifndef NWORKAROUND_ODF_BUGS * KoOdfWorkaround::fixPenWidth(pen, context); * #endif * @endcode */ namespace KoOdfWorkaround { /** * OpenOffice handles a line with the width of 0 as a cosmetic line but in svg it makes the line invisible. * To show it in calligra use a very small line width. However this is not a cosmetic line. */ KRITAFLAKE_EXPORT void fixPenWidth(QPen &pen, KoShapeLoadingContext &context); /** * OpenOffice < 3.0 does not store the draw:enhanced-path for draw:type="ellipse" * Add the path needed for the ellipse */ KRITAFLAKE_EXPORT void fixEnhancedPath(QString &path, const KoXmlElement &element, KoShapeLoadingContext &context); /** * OpenOffice interchanges the position coordinates for polar handles. * According to the specification the first coordinate is the angle, the * second coordinates is the radius. OpenOffice does it the other way around. */ KRITAFLAKE_EXPORT void fixEnhancedPathPolarHandlePosition(QString &position, const KoXmlElement &element, KoShapeLoadingContext &context); KRITAFLAKE_EXPORT bool fixMissingStroke(QPen &pen, const KoXmlElement &element, KoShapeLoadingContext &context, const KoShape *shape = 0); KRITAFLAKE_EXPORT QColor fixMissingFillColor(const KoXmlElement &element, KoShapeLoadingContext &context); KRITAFLAKE_EXPORT bool fixMissingStyle_DisplayLabel(const KoXmlElement &element, KoShapeLoadingContext &context); KRITAFLAKE_EXPORT QSharedPointer fixBackgroundColor(const KoShape *shape, KoShapeLoadingContext &context); /** * Old versions of ooimpress does not set the placeholder for shapes that should have it set - * See open office issue http://www.openoffice.org/issues/show_bug.cgi?id=96406 + * See open office issue https://bz.apache.org/ooo/show_bug.cgi?id=96406 * And kde bug https://bugs.kde.org/show_bug.cgi?id=185354 */ KRITAFLAKE_EXPORT void setFixPresentationPlaceholder(bool fix, KoShapeLoadingContext &context); KRITAFLAKE_EXPORT bool fixPresentationPlaceholder(); KRITAFLAKE_EXPORT void fixPresentationPlaceholder(KoShape *shape); /** * OpenOffice and LibreOffice save gluepoint positions wrong when no align is specified. * According to the specification for the above situation, the position should be saved * as percent values relative to the shapes center point. OpenOffice seems to write * these percent values converted to length units, where the millimeter value corresponds * to the correct percent value (i.e. -5cm = -50mm = -50%). */ KRITAFLAKE_EXPORT void fixGluePointPosition(QString &positionString, KoShapeLoadingContext &context); /** * OpenOffice and LibreOffice does not conform to the specification about default value * of the svg:fill-rule. If this attribute is missing, according the spec, the initial * value is nonzero, but OOo uses evenodd. Because we are conform to the spec, we need * to set what OOo display. * See http://www.w3.org/TR/SVG/painting.html#FillRuleProperty */ KRITAFLAKE_EXPORT void fixMissingFillRule(Qt::FillRule &fillRule, KoShapeLoadingContext &context); /** * OpenOffice resizes text shapes with autogrow in both directions. If the text box is saved to * small the text will not fit and it needs to be adjusted during the first layout. * This methods returns true if we need to adjust the layout. The adjusting is handled at a different place. */ KRITAFLAKE_EXPORT bool fixAutoGrow(KoTextShapeDataBase::ResizeMethod method, KoShapeLoadingContext &context); /** * OpenOffice and LibreOffice do not set the svg:width, svg:height, svg:x and svg:y correctly when saving * parts of draw:ellipses or draw:circle * This method returns true when the width, height, x and y is given for the full circle */ KRITAFLAKE_EXPORT bool fixEllipse(const QString &kind, KoShapeLoadingContext &context); /** * Calligra did use the bad strings "Formula.hidden" and "protected Formula.hidden" as values * for style:cell-protect, instead of "formula-hidden" and "protected formula-hidden". * This method fixes the bad strings to the correct ones. */ KRITAFLAKE_EXPORT void fixBadFormulaHiddenForStyleCellProtect(QString &value); /** * Calligra used to store text:time-value with a "0-00-00T" prefix * This method removes that prefix. */ KRITAFLAKE_EXPORT void fixBadDateForTextTime(QString &value); /** * OpenOffice.org used to write the "rect(...)" value for fo:clip without * separating the 4 offset values by commas. * This method changes the string with the offset values to have commas as separators. */ KRITAFLAKE_EXPORT void fixClipRectOffsetValuesString(QString &offsetValuesString); /** * LibreOffice used to write text:style-name attribute for table:table-template element, * which is not a valid attribute for the element. */ KRITAFLAKE_EXPORT QString fixTableTemplateName(const KoXmlElement &e); /** * LibreOffice used to write text:style-name attribute for * table:first-row, table:last-row, table:first-column, * table:last-column, table:odd-rows, table:odd-columns, * table:body elements, which is not a valid attribute for the element. */ KRITAFLAKE_EXPORT QString fixTableTemplateCellStyleName(const KoXmlElement &e); /** * LibreOffice used to have a bug with handling of z command in svg path. * This resulted in broken marker path used (and copied also to Calligra). * This methods substitutes known old marker paths with the latest (fixed) * path variant. */ KRITAFLAKE_EXPORT void fixMarkerPath(QString &path); } #endif /* KOODFWORKAROUND_H */ diff --git a/libs/flake/KoShapeAnchor.cpp b/libs/flake/KoShapeAnchor.cpp index a69d2cbf03..a393e26235 100644 --- a/libs/flake/KoShapeAnchor.cpp +++ b/libs/flake/KoShapeAnchor.cpp @@ -1,528 +1,528 @@ /* This file is part of the KDE project * Copyright (C) 2007, 2009-2010 Thomas Zander * Copyright (C) 2010 Ko Gmbh * Copyright (C) 2011 Matus Hanzes * Copyright (C) 2013 C. Boemann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeAnchor.h" #include "KoStyleStack.h" #include "KoOdfLoadingContext.h" #include #include #include #include #include #include #include #include #include #include class Q_DECL_HIDDEN KoShapeAnchor::Private { public: Private(KoShape *s) : shape(s) , verticalPos(KoShapeAnchor::VTop) , verticalRel(KoShapeAnchor::VLine) , horizontalPos(KoShapeAnchor::HLeft) , horizontalRel(KoShapeAnchor::HChar) , flowWithText(true) , anchorType(KoShapeAnchor::AnchorToCharacter) , placementStrategy(0) , pageNumber(-1) , textLocation(0) { } QDebug printDebug(QDebug dbg) const { #ifndef NDEBUG dbg.space() << "KoShapeAnchor" << this; dbg.space() << "offset:" << offset; dbg.space() << "shape:" << shape->name(); #endif return dbg.space(); } KoShape * const shape; QPointF offset; KoShapeAnchor::VerticalPos verticalPos; KoShapeAnchor::VerticalRel verticalRel; KoShapeAnchor::HorizontalPos horizontalPos; KoShapeAnchor::HorizontalRel horizontalRel; QString wrapInfluenceOnPosition; bool flowWithText; KoShapeAnchor::AnchorType anchorType; KoShapeAnchor::PlacementStrategy *placementStrategy; int pageNumber; KoShapeAnchor::TextLocation *textLocation; }; KoShapeAnchor::KoShapeAnchor(KoShape *shape) : d(new Private(shape)) { } KoShapeAnchor::~KoShapeAnchor() { if (d->placementStrategy != 0) { delete d->placementStrategy; } delete d; } KoShape *KoShapeAnchor::shape() const { return d->shape; } KoShapeAnchor::AnchorType KoShapeAnchor::anchorType() const { return d->anchorType; } void KoShapeAnchor::setHorizontalPos(HorizontalPos hp) { d->horizontalPos = hp; } KoShapeAnchor::HorizontalPos KoShapeAnchor::horizontalPos() const { return d->horizontalPos; } void KoShapeAnchor::setHorizontalRel(HorizontalRel hr) { d->horizontalRel = hr; } KoShapeAnchor::HorizontalRel KoShapeAnchor::horizontalRel() const { return d->horizontalRel; } void KoShapeAnchor::setVerticalPos(VerticalPos vp) { d->verticalPos = vp; } KoShapeAnchor::VerticalPos KoShapeAnchor::verticalPos() const { return d->verticalPos; } void KoShapeAnchor::setVerticalRel(VerticalRel vr) { d->verticalRel = vr; } KoShapeAnchor::VerticalRel KoShapeAnchor::verticalRel() const { return d->verticalRel; } QString KoShapeAnchor::wrapInfluenceOnPosition() const { return d->wrapInfluenceOnPosition; } bool KoShapeAnchor::flowWithText() const { return d->flowWithText; } int KoShapeAnchor::pageNumber() const { return d->pageNumber; } const QPointF &KoShapeAnchor::offset() const { return d->offset; } void KoShapeAnchor::setOffset(const QPointF &offset) { d->offset = offset; } void KoShapeAnchor::saveOdf(KoShapeSavingContext &context) const { // anchor-type switch (d->anchorType) { case AnchorToCharacter: shape()->setAdditionalAttribute("text:anchor-type", "char"); break; case AnchorAsCharacter: shape()->setAdditionalAttribute("text:anchor-type", "as-char"); break; case AnchorParagraph: shape()->setAdditionalAttribute("text:anchor-type", "paragraph"); break; case AnchorPage: shape()->setAdditionalAttribute("text:anchor-type", "page"); break; default: break; } // vertical-pos switch (d->verticalPos) { case VBelow: shape()->setAdditionalStyleAttribute("style:vertical-pos", "below"); break; case VBottom: shape()->setAdditionalStyleAttribute("style:vertical-pos", "bottom"); break; case VFromTop: shape()->setAdditionalStyleAttribute("style:vertical-pos", "from-top"); break; case VMiddle: shape()->setAdditionalStyleAttribute("style:vertical-pos", "middle"); break; case VTop: shape()->setAdditionalStyleAttribute("style:vertical-pos", "top"); break; default: break; } // vertical-rel switch (d->verticalRel) { case VBaseline: shape()->setAdditionalStyleAttribute("style:vertical-rel", "baseline"); break; case VChar: shape()->setAdditionalStyleAttribute("style:vertical-rel", "char"); break; case VFrame: shape()->setAdditionalStyleAttribute("style:vertical-rel", "frame"); break; case VFrameContent: shape()->setAdditionalStyleAttribute("style:vertical-rel", "frame-content"); break; case VLine: shape()->setAdditionalStyleAttribute("style:vertical-rel", "line"); break; case VPage: shape()->setAdditionalStyleAttribute("style:vertical-rel", "page"); break; case VPageContent: shape()->setAdditionalStyleAttribute("style:vertical-rel", "page-content"); break; case VParagraph: shape()->setAdditionalStyleAttribute("style:vertical-rel", "paragraph"); break; case VParagraphContent: shape()->setAdditionalStyleAttribute("style:vertical-rel", "paragraph-content"); break; case VText: shape()->setAdditionalStyleAttribute("style:vertical-rel", "text"); break; default: break; } // horizontal-pos switch (d->horizontalPos) { case HCenter: shape()->setAdditionalStyleAttribute("style:horizontal-pos", "center"); break; case HFromInside: shape()->setAdditionalStyleAttribute("style:horizontal-pos", "from-inside"); break; case HFromLeft: shape()->setAdditionalStyleAttribute("style:horizontal-pos", "from-left"); break; case HInside: shape()->setAdditionalStyleAttribute("style:horizontal-posl", "inside"); break; case HLeft: shape()->setAdditionalStyleAttribute("style:horizontal-pos", "left"); break; case HOutside: shape()->setAdditionalStyleAttribute("style:horizontal-pos", "outside"); break; case HRight: shape()->setAdditionalStyleAttribute("style:horizontal-pos", "right"); break; default: break; } // horizontal-rel switch (d->horizontalRel) { case HChar: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "char"); break; case HPage: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "page"); break; case HPageContent: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "page-content"); break; case HPageStartMargin: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "page-start-margin"); break; case HPageEndMargin: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "page-end-margin"); break; case HFrame: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "frame"); break; case HFrameContent: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "frame-content"); break; case HFrameEndMargin: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "frame-end-margin"); break; case HFrameStartMargin: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "frame-start-margin"); break; case HParagraph: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "paragraph"); break; case HParagraphContent: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "paragraph-content"); break; case HParagraphEndMargin: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "paragraph-end-margin"); break; case HParagraphStartMargin: shape()->setAdditionalStyleAttribute("style:horizontal-rel", "paragraph-start-margin"); break; default: break; } if (!d->wrapInfluenceOnPosition.isEmpty()) { shape()->setAdditionalStyleAttribute("draw:wrap-influence-on-position", d->wrapInfluenceOnPosition); } if (d->flowWithText) { shape()->setAdditionalStyleAttribute("style:flow-with-text", "true"); } else { shape()->setAdditionalStyleAttribute("style:flow-with-text", "false"); } if (shape()->parent()) {// an anchor may not yet have been layout-ed QTransform parentMatrix = shape()->parent()->absoluteTransformation().inverted(); QTransform shapeMatrix = shape()->absoluteTransformation(); qreal dx = d->offset.x() - shapeMatrix.dx()*parentMatrix.m11() - shapeMatrix.dy()*parentMatrix.m21(); qreal dy = d->offset.y() - shapeMatrix.dx()*parentMatrix.m12() - shapeMatrix.dy()*parentMatrix.m22(); context.addShapeOffset(shape(), QTransform(parentMatrix.m11(),parentMatrix.m12(), parentMatrix.m21(),parentMatrix.m22(), dx,dy)); } shape()->saveOdf(context); context.removeShapeOffset(shape()); } bool KoShapeAnchor::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { d->offset = shape()->position(); QString anchorType = shape()->additionalAttribute("text:anchor-type"); if (anchorType == "char") { d->anchorType = AnchorToCharacter; } else if (anchorType == "as-char") { d->anchorType = AnchorAsCharacter; d->horizontalRel = HChar; d->horizontalPos = HLeft; } else if (anchorType == "paragraph") { d->anchorType = AnchorParagraph; } else { d->anchorType = AnchorPage; // it has different defaults at least LO thinks so - ODF doesn't define defaults for this d->horizontalPos = HFromLeft; d->verticalPos = VFromTop; d->horizontalRel = HPage; d->verticalRel = VPage; } if (anchorType == "page" && shape()->hasAdditionalAttribute("text:anchor-page-number")) { d->pageNumber = shape()->additionalAttribute("text:anchor-page-number").toInt(); if (d->pageNumber <= 0) { // invalid if the page-number is invalid (OO.org does the same) - // see http://bugs.kde.org/show_bug.cgi?id=281869 + // see https://bugs.kde.org/show_bug.cgi?id=281869 d->pageNumber = -1; } } else { d->pageNumber = -1; } // always make it invisible or it will create empty rects on the first page // during initial layout. This is because only when we layout it's final page is // the shape moved away from page 1 // in KWRootAreaProvider of textlayout it's set back to visible shape()->setVisible(false); // load settings from graphic style KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.save(); if (element.hasAttributeNS(KoXmlNS::draw, "style-name")) { context.odfLoadingContext().fillStyleStack(element, KoXmlNS::draw, "style-name", "graphic"); styleStack.setTypeProperties("graphic"); } QString verticalPos = styleStack.property(KoXmlNS::style, "vertical-pos"); QString verticalRel = styleStack.property(KoXmlNS::style, "vertical-rel"); QString horizontalPos = styleStack.property(KoXmlNS::style, "horizontal-pos"); QString horizontalRel = styleStack.property(KoXmlNS::style, "horizontal-rel"); d->wrapInfluenceOnPosition = styleStack.property(KoXmlNS::draw, "wrap-influence-on-position"); QString flowWithText = styleStack.property(KoXmlNS::style, "flow-with-text"); d->flowWithText = flowWithText.isEmpty() ? false : flowWithText == "true"; styleStack.restore(); // vertical-pos if (verticalPos == "below") {//svg:y attribute is ignored d->verticalPos = VBelow; d->offset.setY(0); } else if (verticalPos == "bottom") {//svg:y attribute is ignored d->verticalPos = VBottom; d->offset.setY(-shape()->size().height()); } else if (verticalPos == "from-top") { d->verticalPos = VFromTop; } else if (verticalPos == "middle") {//svg:y attribute is ignored d->verticalPos = VMiddle; d->offset.setY(-(shape()->size().height()/2)); } else if (verticalPos == "top") {//svg:y attribute is ignored d->verticalPos = VTop; d->offset.setY(0); } // vertical-rel if (verticalRel == "baseline") d->verticalRel = VBaseline; else if (verticalRel == "char") d->verticalRel = VChar; else if (verticalRel == "frame") d->verticalRel = VFrame; else if (verticalRel == "frame-content") d->verticalRel = VFrameContent; else if (verticalRel == "line") d->verticalRel = VLine; else if (verticalRel == "page") d->verticalRel = VPage; else if (verticalRel == "page-content") d->verticalRel = VPageContent; else if (verticalRel == "paragraph") d->verticalRel = VParagraph; else if (verticalRel == "paragraph-content") d->verticalRel = VParagraphContent; else if (verticalRel == "text") d->verticalRel = VText; // horizontal-pos if (horizontalPos == "center") {//svg:x attribute is ignored d->horizontalPos = HCenter; d->offset.setX(-(shape()->size().width()/2)); } else if (horizontalPos == "from-inside") { d->horizontalPos = HFromInside; } else if (horizontalPos == "from-left") { d->horizontalPos = HFromLeft; } else if (horizontalPos == "inside") {//svg:x attribute is ignored d->horizontalPos = HInside; d->offset.setX(0); } else if (horizontalPos == "left") {//svg:x attribute is ignored d->horizontalPos = HLeft; d->offset.setX(0); }else if (horizontalPos == "outside") {//svg:x attribute is ignored d->horizontalPos = HOutside; d->offset.setX(-shape()->size().width()); }else if (horizontalPos == "right") {//svg:x attribute is ignored d->horizontalPos = HRight; d->offset.setX(-shape()->size().width()); } // horizontal-rel if (horizontalRel == "char") d->horizontalRel = HChar; else if (horizontalRel == "page") d->horizontalRel = HPage; else if (horizontalRel == "page-content") d->horizontalRel = HPageContent; else if (horizontalRel == "page-start-margin") d->horizontalRel = HPageStartMargin; else if (horizontalRel == "page-end-margin") d->horizontalRel = HPageEndMargin; else if (horizontalRel == "frame") d->horizontalRel = HFrame; else if (horizontalRel == "frame-content") d->horizontalRel = HFrameContent; else if (horizontalRel == "frame-end-margin") d->horizontalRel = HFrameEndMargin; else if (horizontalRel == "frame-start-margin") d->horizontalRel = HFrameStartMargin; else if (horizontalRel == "paragraph") d->horizontalRel = HParagraph; else if (horizontalRel == "paragraph-content") d->horizontalRel = HParagraphContent; else if (horizontalRel == "paragraph-end-margin") d->horizontalRel = HParagraphEndMargin; else if (horizontalRel == "paragraph-start-margin") d->horizontalRel = HParagraphStartMargin; // if svg:x or svg:y should be ignored set new position shape()->setPosition(d->offset); return true; } void KoShapeAnchor::setAnchorType(KoShapeAnchor::AnchorType type) { d->anchorType = type; if (type == AnchorAsCharacter) { d->horizontalRel = HChar; d->horizontalPos = HLeft; } } KoShapeAnchor::TextLocation *KoShapeAnchor::textLocation() const { return d->textLocation; } void KoShapeAnchor::setTextLocation(TextLocation *textLocation) { d->textLocation = textLocation; } KoShapeAnchor::PlacementStrategy *KoShapeAnchor::placementStrategy() const { return d->placementStrategy; } void KoShapeAnchor::setPlacementStrategy(PlacementStrategy *placementStrategy) { if (placementStrategy != d->placementStrategy) { delete d->placementStrategy; d->placementStrategy = placementStrategy; } } diff --git a/libs/flake/KoShapeShadow.cpp b/libs/flake/KoShapeShadow.cpp index 0b3afc058d..30ebe2291d 100644 --- a/libs/flake/KoShapeShadow.cpp +++ b/libs/flake/KoShapeShadow.cpp @@ -1,356 +1,356 @@ /* This file is part of the KDE project * Copyright (C) 2008-2009 Jan Hambrecht * Copyright (C) 2010 Thomas Zander * Copyright (C) 2010 Ariya Hidayat * Copyright (C) 2010-2011 Yue Liu * * 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 "KoShapeShadow.h" #include "KoShapeGroup.h" #include "KoSelection.h" #include "KoShapeSavingContext.h" #include "KoShapeStrokeModel.h" #include "KoShape.h" #include "KoInsets.h" #include "KoPathShape.h" #include #include #include #include #include #include class Q_DECL_HIDDEN KoShapeShadow::Private { public: Private() : offset(2, 2), color(Qt::black), blur(8), visible(true), refCount(0) { } QPointF offset; QColor color; qreal blur; bool visible; QAtomicInt refCount; /** * Paints the shadow of the shape group to the buffer image. * @param group the shape group to paint around * @param painter the painter to paint on the image * @param converter to convert between internal and view coordinates. */ void paintGroupShadow(KoShapeGroup *group, QPainter &painter); /** * Paints the shadow of the shape to the buffer image. * @param shape the shape to paint around * @param painter the painter to paint on the image */ void paintShadow(KoShape *shape, QPainter &painter); void blurShadow(QImage &image, int radius, const QColor& shadowColor); }; void KoShapeShadow::Private::paintGroupShadow(KoShapeGroup *group, QPainter &painter) { QList shapes = group->shapes(); Q_FOREACH (KoShape *child, shapes) { // we paint recursively here, so we do not have to check recursively for visibility if (!child->isVisible(false)) continue; painter.save(); //apply group child's transformation painter.setTransform(child->absoluteTransformation(), true); paintShadow(child, painter); painter.restore(); } } void KoShapeShadow::Private::paintShadow(KoShape *shape, QPainter &painter) { QPainterPath path(shape->shadowOutline()); if (!path.isEmpty()) { painter.save(); painter.setBrush(QBrush(color)); // Make sure the shadow has the same fill rule as the shape. KoPathShape * pathShape = dynamic_cast(shape); if (pathShape) path.setFillRule(pathShape->fillRule()); painter.drawPath(path); painter.restore(); } if (shape->stroke()) { shape->stroke()->paint(shape, painter); } } /* You can also find a BSD version to this method from - * http://gitorious.org/ofi-labs/x2/blobs/master/graphics/shadowblur/ + * https://github.com/ariya/X2/tree/master/graphics/shadowblur */ void KoShapeShadow::Private::blurShadow(QImage &image, int radius, const QColor& shadowColor) { static const int BlurSumShift = 15; // Check http://www.w3.org/TR/SVG/filters.html# // As noted in the SVG filter specification, ru // approximates a real gaussian blur nicely. - // See comments in http://webkit.org/b/40793, it seems sensible + // See comments in https://bugs.webkit.org/show_bug.cgi?id=40793, it seems sensible // to follow Skia's limit of 128 pixels for the blur radius. if (radius > 128) radius = 128; int channels[4] = { 3, 0, 1, 3 }; int dmax = radius >> 1; int dmin = dmax - 1 + (radius & 1); if (dmin < 0) dmin = 0; // Two stages: horizontal and vertical for (int k = 0; k < 2; ++k) { unsigned char* pixels = image.bits(); int stride = (k == 0) ? 4 : image.bytesPerLine(); int delta = (k == 0) ? image.bytesPerLine() : 4; int jfinal = (k == 0) ? image.height() : image.width(); int dim = (k == 0) ? image.width() : image.height(); for (int j = 0; j < jfinal; ++j, pixels += delta) { // For each step, we blur the alpha in a channel and store the result // in another channel for the subsequent step. // We use sliding window algorithm to accumulate the alpha values. // This is much more efficient than computing the sum of each pixels // covered by the box kernel size for each x. for (int step = 0; step < 3; ++step) { int side1 = (step == 0) ? dmin : dmax; int side2 = (step == 1) ? dmin : dmax; int pixelCount = side1 + 1 + side2; int invCount = ((1 << BlurSumShift) + pixelCount - 1) / pixelCount; int ofs = 1 + side2; int alpha1 = pixels[channels[step]]; int alpha2 = pixels[(dim - 1) * stride + channels[step]]; unsigned char* ptr = pixels + channels[step + 1]; unsigned char* prev = pixels + stride + channels[step]; unsigned char* next = pixels + ofs * stride + channels[step]; int i; int sum = side1 * alpha1 + alpha1; int limit = (dim < side2 + 1) ? dim : side2 + 1; for (i = 1; i < limit; ++i, prev += stride) sum += *prev; if (limit <= side2) sum += (side2 - limit + 1) * alpha2; limit = (side1 < dim) ? side1 : dim; for (i = 0; i < limit; ptr += stride, next += stride, ++i, ++ofs) { *ptr = (sum * invCount) >> BlurSumShift; sum += ((ofs < dim) ? *next : alpha2) - alpha1; } prev = pixels + channels[step]; for (; ofs < dim; ptr += stride, prev += stride, next += stride, ++i, ++ofs) { *ptr = (sum * invCount) >> BlurSumShift; sum += (*next) - (*prev); } for (; i < dim; ptr += stride, prev += stride, ++i) { *ptr = (sum * invCount) >> BlurSumShift; sum += alpha2 - (*prev); } } } } // "Colorize" with the right shadow color. QPainter p(&image); p.setCompositionMode(QPainter::CompositionMode_SourceIn); p.fillRect(image.rect(), shadowColor); p.end(); } // ---------------------------------------------------------------- // KoShapeShadow KoShapeShadow::KoShapeShadow() : d(new Private()) { } KoShapeShadow::~KoShapeShadow() { delete d; } KoShapeShadow::KoShapeShadow(const KoShapeShadow &rhs) : d(new Private(*rhs.d)) { d->refCount = 0; } KoShapeShadow& KoShapeShadow::operator=(const KoShapeShadow &rhs) { *d = *rhs.d; d->refCount = 0; return *this; } void KoShapeShadow::fillStyle(KoGenStyle &style, KoShapeSavingContext &context) { Q_UNUSED(context); style.addProperty("draw:shadow", d->visible ? "visible" : "hidden", KoGenStyle::GraphicType); style.addProperty("draw:shadow-color", d->color.name(), KoGenStyle::GraphicType); if (d->color.alphaF() != 1.0) style.addProperty("draw:shadow-opacity", QString("%1%").arg(d->color.alphaF() * 100.0), KoGenStyle::GraphicType); style.addProperty("draw:shadow-offset-x", QString("%1pt").arg(d->offset.x()), KoGenStyle::GraphicType); style.addProperty("draw:shadow-offset-y", QString("%1pt").arg(d->offset.y()), KoGenStyle::GraphicType); if (d->blur != 0) style.addProperty("calligra:shadow-blur-radius", QString("%1pt").arg(d->blur), KoGenStyle::GraphicType); } void KoShapeShadow::paint(KoShape *shape, QPainter &painter) { if (! d->visible) return; // So the approach we are taking here is to draw into a buffer image the size of boundingRect // We offset by the shadow offset at the time we draw into the buffer // Then we filter the image and draw it at the position of the bounding rect on canvas QTransform documentToView = painter.transform(); //the boundingRect of the shape or the KoSelection boundingRect of the group QRectF shadowRect = shape->boundingRect(); QRectF zoomedClipRegion = documentToView.mapRect(shadowRect); // Init the buffer image QImage sourceGraphic(zoomedClipRegion.size().toSize(), QImage::Format_ARGB32_Premultiplied); sourceGraphic.fill(qRgba(0,0,0,0)); // Init the buffer painter QPainter imagePainter(&sourceGraphic); imagePainter.setPen(Qt::NoPen); imagePainter.setBrush(Qt::NoBrush); imagePainter.setRenderHint(QPainter::Antialiasing, painter.testRenderHint(QPainter::Antialiasing)); // Since our imagebuffer and the canvas don't align we need to offset our drawings imagePainter.translate(-1.0f*documentToView.map(shadowRect.topLeft())); // Handle the shadow offset imagePainter.translate(documentToView.map(offset())); KoShapeGroup *group = dynamic_cast(shape); if (group) { d->paintGroupShadow(group, imagePainter); } else { //apply shape's transformation imagePainter.setTransform(shape->absoluteTransformation(), true); d->paintShadow(shape, imagePainter); } imagePainter.end(); // Blur the shadow (well the entire buffer) d->blurShadow(sourceGraphic, qRound(documentToView.m11() * d->blur), d->color); // Paint the result painter.save(); // The painter is initialized for us with canvas transform 'plus' shape transform // we are only interested in the canvas transform so 'subtract' the shape transform part painter.setTransform(shape->absoluteTransformation().inverted() * painter.transform()); painter.drawImage(zoomedClipRegion.topLeft(), sourceGraphic); painter.restore(); } void KoShapeShadow::setOffset(const QPointF & offset) { d->offset = offset; } QPointF KoShapeShadow::offset() const { return d->offset; } void KoShapeShadow::setColor(const QColor &color) { d->color = color; } QColor KoShapeShadow::color() const { return d->color; } void KoShapeShadow::setBlur(qreal blur) { // force positive blur radius d->blur = qAbs(blur); } qreal KoShapeShadow::blur() const { return d->blur; } void KoShapeShadow::setVisible(bool visible) { d->visible = visible; } bool KoShapeShadow::isVisible() const { return d->visible; } void KoShapeShadow::insets(KoInsets &insets) const { if (!d->visible) { insets.top = 0; insets.bottom = 0; insets.left = 0; insets.right = 0; return; } qreal expand = d->blur; insets.left = (d->offset.x() < 0.0) ? qAbs(d->offset.x()) : 0.0; insets.top = (d->offset.y() < 0.0) ? qAbs(d->offset.y()) : 0.0; insets.right = (d->offset.x() > 0.0) ? d->offset.x() : 0.0; insets.bottom = (d->offset.y() > 0.0) ? d->offset.y() : 0.0; insets.left += expand; insets.top += expand; insets.right += expand; insets.bottom += expand; } bool KoShapeShadow::ref() { return d->refCount.ref(); } bool KoShapeShadow::deref() { return d->refCount.deref(); } int KoShapeShadow::useCount() const { return d->refCount; } diff --git a/libs/flake/resources/KoSvgSymbolCollectionResource.cpp b/libs/flake/resources/KoSvgSymbolCollectionResource.cpp index 0a5e60fbbd..f2acf5d46e 100644 --- a/libs/flake/resources/KoSvgSymbolCollectionResource.cpp +++ b/libs/flake/resources/KoSvgSymbolCollectionResource.cpp @@ -1,233 +1,233 @@ /* This file is part of the KDE project - Copyright (c) 2017 L. E. Segovia + Copyright (c) 2017 L. E. Segovia This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_debug.h" #include #include #include #include #include #include QImage KoSvgSymbol::icon() { KoShapeGroup *group = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(group, QImage()); QRectF rc = group->boundingRect().normalized(); QImage image(rc.width(), rc.height(), QImage::Format_ARGB32_Premultiplied); QPainter gc(&image); image.fill(Qt::gray); KoShapePaintingContext ctx; // debugFlake << "Going to render. Original bounding rect:" << group->boundingRect() // << "Normalized: " << rc // << "Scale W" << 256 / rc.width() << "Scale H" << 256 / rc.height(); gc.translate(-rc.x(), -rc.y()); KoShapeManager::renderSingleShape(group, gc, ctx); gc.end(); image = image.scaled(128, 128, Qt::KeepAspectRatio); return image; } struct KoSvgSymbolCollectionResource::Private { QVector symbols; QString title; QString description; }; KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource(const QString& filename) : KoResource(filename) , d(new Private()) { } KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource() : KoResource(QString()) , d(new Private()) { } KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource(const KoSvgSymbolCollectionResource& rhs) : QObject(0) , KoResource(QString()) , d(new Private()) { setFilename(rhs.filename()); d->symbols = rhs.d->symbols; setValid(true); } KoSvgSymbolCollectionResource::~KoSvgSymbolCollectionResource() { } bool KoSvgSymbolCollectionResource::load() { QFile file(filename()); if (file.size() == 0) return false; if (!file.open(QIODevice::ReadOnly)) { return false; } bool res = loadFromDevice(&file); file.close(); return res; } bool KoSvgSymbolCollectionResource::loadFromDevice(QIODevice *dev) { if (!dev->isOpen()) dev->open(QIODevice::ReadOnly); QString errorMsg; int errorLine = 0; int errorColumn; KoXmlDocument doc = SvgParser::createDocumentFromSvg(dev, &errorMsg, &errorLine, &errorColumn); if (doc.isNull()) { errKrita << "Parsing error in " << filename() << "! 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); return false; } KoDocumentResourceManager manager; SvgParser parser(&manager); parser.setResolution(QRectF(0,0,100,100), 72); // initialize with default values QSizeF fragmentSize; // We're not interested in the shapes themselves qDeleteAll(parser.parseSvg(doc.documentElement(), &fragmentSize)); d->symbols = parser.takeSymbols(); // debugFlake << "Loaded" << filename() << "\n\t" // << "Title" << parser.documentTitle() << "\n\t" // << "Description" << parser.documentDescription() // << "\n\tgot" << d->symbols.size() << "symbols" // << d->symbols[0]->shape->outlineRect() // << d->symbols[0]->shape->size(); d->title = parser.documentTitle(); setName(d->title); d->description = parser.documentDescription(); if (d->symbols.size() < 1) { setValid(false); return false; } setValid(true); setImage(d->symbols[0]->icon()); return true; } bool KoSvgSymbolCollectionResource::save() { QFile file(filename()); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return false; } saveToDevice(&file); file.close(); return true; } bool KoSvgSymbolCollectionResource::saveToDevice(QIODevice *dev) const { bool res = false; // XXX if (res) { KoResource::saveToDevice(dev); } return res; } QString KoSvgSymbolCollectionResource::defaultFileExtension() const { return QString(".svg"); } QString KoSvgSymbolCollectionResource::title() const { return d->title; } QString KoSvgSymbolCollectionResource::description() const { return d->description; } QString KoSvgSymbolCollectionResource::creator() const { return ""; } QString KoSvgSymbolCollectionResource::rights() const { return ""; } QString KoSvgSymbolCollectionResource::language() const { return ""; } QStringList KoSvgSymbolCollectionResource::subjects() const { return QStringList(); } QString KoSvgSymbolCollectionResource::license() const { return ""; } QStringList KoSvgSymbolCollectionResource::permits() const { return QStringList(); } QVector KoSvgSymbolCollectionResource::symbols() const { return d->symbols; } diff --git a/libs/flake/svg/SvgUtil.cpp b/libs/flake/svg/SvgUtil.cpp index 892b39db88..c59700f8f9 100644 --- a/libs/flake/svg/SvgUtil.cpp +++ b/libs/flake/svg/SvgUtil.cpp @@ -1,527 +1,527 @@ /* 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 "SvgUtil.h" #include "SvgGraphicContext.h" #include #include #include #include #include #include #include #include #include "kis_debug.h" #include "kis_global.h" #include #include "kis_dom_utils.h" #define DPI 72.0 #define DEG2RAD(degree) degree/180.0*M_PI double SvgUtil::fromUserSpace(double value) { return value; } double SvgUtil::toUserSpace(double value) { return value; } double SvgUtil::ptToPx(SvgGraphicsContext *gc, double value) { return value * gc->pixelsPerInch / DPI; } QPointF SvgUtil::toUserSpace(const QPointF &point) { return QPointF(toUserSpace(point.x()), toUserSpace(point.y())); } QRectF SvgUtil::toUserSpace(const QRectF &rect) { return QRectF(toUserSpace(rect.topLeft()), toUserSpace(rect.size())); } QSizeF SvgUtil::toUserSpace(const QSizeF &size) { return QSizeF(toUserSpace(size.width()), toUserSpace(size.height())); } QString SvgUtil::toPercentage(qreal value) { return KisDomUtils::toString(value * 100.0) + "%"; } double SvgUtil::fromPercentage(QString s) { if (s.endsWith('%')) return KisDomUtils::toDouble(s.remove('%')) / 100.0; else return KisDomUtils::toDouble(s); } QPointF SvgUtil::objectToUserSpace(const QPointF &position, const QRectF &objectBound) { qreal x = objectBound.left() + position.x() * objectBound.width(); qreal y = objectBound.top() + position.y() * objectBound.height(); return QPointF(x, y); } QSizeF SvgUtil::objectToUserSpace(const QSizeF &size, const QRectF &objectBound) { qreal w = size.width() * objectBound.width(); qreal h = size.height() * objectBound.height(); return QSizeF(w, h); } QPointF SvgUtil::userSpaceToObject(const QPointF &position, const QRectF &objectBound) { qreal x = 0.0; if (objectBound.width() != 0) x = (position.x() - objectBound.x()) / objectBound.width(); qreal y = 0.0; if (objectBound.height() != 0) y = (position.y() - objectBound.y()) / objectBound.height(); return QPointF(x, y); } QSizeF SvgUtil::userSpaceToObject(const QSizeF &size, const QRectF &objectBound) { qreal w = objectBound.width() != 0 ? size.width() / objectBound.width() : 0.0; qreal h = objectBound.height() != 0 ? size.height() / objectBound.height() : 0.0; return QSizeF(w, h); } QString SvgUtil::transformToString(const QTransform &transform) { if (transform.isIdentity()) return QString(); if (transform.type() == QTransform::TxTranslate) { return QString("translate(%1, %2)") .arg(KisDomUtils::toString(toUserSpace(transform.dx()))) .arg(KisDomUtils::toString(toUserSpace(transform.dy()))); } else { return QString("matrix(%1 %2 %3 %4 %5 %6)") .arg(KisDomUtils::toString(transform.m11())) .arg(KisDomUtils::toString(transform.m12())) .arg(KisDomUtils::toString(transform.m21())) .arg(KisDomUtils::toString(transform.m22())) .arg(KisDomUtils::toString(toUserSpace(transform.dx()))) .arg(KisDomUtils::toString(toUserSpace(transform.dy()))); } } void SvgUtil::writeTransformAttributeLazy(const QString &name, const QTransform &transform, KoXmlWriter &shapeWriter) { const QString value = transformToString(transform); if (!value.isEmpty()) { shapeWriter.addAttribute(name.toLatin1().data(), value); } } bool SvgUtil::parseViewBox(const KoXmlElement &e, const QRectF &elementBounds, QRectF *_viewRect, QTransform *_viewTransform) { KIS_ASSERT(_viewRect); KIS_ASSERT(_viewTransform); QString viewBoxStr = e.attribute("viewBox"); if (viewBoxStr.isEmpty()) return false; bool result = false; QRectF viewBoxRect; // this is a workaround for bug 260429 for a file generated by blender // who has px in the viewbox which is wrong. - // reported as bug http://projects.blender.org/tracker/?group_id=9&atid=498&func=detail&aid=30971 + // reported as bug https://developer.blender.org/T30971 viewBoxStr.remove("px"); QStringList points = viewBoxStr.replace(',', ' ').simplified().split(' '); if (points.count() == 4) { viewBoxRect.setX(SvgUtil::fromUserSpace(points[0].toFloat())); viewBoxRect.setY(SvgUtil::fromUserSpace(points[1].toFloat())); viewBoxRect.setWidth(SvgUtil::fromUserSpace(points[2].toFloat())); viewBoxRect.setHeight(SvgUtil::fromUserSpace(points[3].toFloat())); result = true; } else { // TODO: WARNING! } if (!result) return false; QTransform viewBoxTransform = QTransform::fromTranslate(-viewBoxRect.x(), -viewBoxRect.y()) * QTransform::fromScale(elementBounds.width() / viewBoxRect.width(), elementBounds.height() / viewBoxRect.height()) * QTransform::fromTranslate(elementBounds.x(), elementBounds.y()); const QString aspectString = e.attribute("preserveAspectRatio"); // give initial value if value not defined PreserveAspectRatioParser p( (!aspectString.isEmpty())? aspectString : QString("xMidYMid meet")); parseAspectRatio(p, elementBounds, viewBoxRect, &viewBoxTransform); *_viewRect = viewBoxRect; *_viewTransform = viewBoxTransform; return result; } void SvgUtil::parseAspectRatio(const PreserveAspectRatioParser &p, const QRectF &elementBounds, const QRectF &viewBoxRect, QTransform *_viewTransform) { if (p.mode != Qt::IgnoreAspectRatio) { QTransform viewBoxTransform = *_viewTransform; const qreal tan1 = viewBoxRect.height() / viewBoxRect.width(); const qreal tan2 = elementBounds.height() / elementBounds.width(); const qreal uniformScale = (p.mode == Qt::KeepAspectRatioByExpanding) ^ (tan1 > tan2) ? elementBounds.height() / viewBoxRect.height() : elementBounds.width() / viewBoxRect.width(); viewBoxTransform = QTransform::fromTranslate(-viewBoxRect.x(), -viewBoxRect.y()) * QTransform::fromScale(uniformScale, uniformScale) * QTransform::fromTranslate(elementBounds.x(), elementBounds.y()); const QPointF viewBoxAnchor = viewBoxTransform.map(p.rectAnchorPoint(viewBoxRect)); const QPointF elementAnchor = p.rectAnchorPoint(elementBounds); const QPointF offset = elementAnchor - viewBoxAnchor; viewBoxTransform = viewBoxTransform * QTransform::fromTranslate(offset.x(), offset.y()); *_viewTransform = viewBoxTransform; } } qreal SvgUtil::parseUnit(SvgGraphicsContext *gc, const QString &unit, bool horiz, bool vert, const QRectF &bbox) { if (unit.isEmpty()) return 0.0; QByteArray unitLatin1 = unit.toLatin1(); // TODO : percentage? const char *start = unitLatin1.data(); if (!start) { return 0.0; } qreal value = 0.0; const char *end = parseNumber(start, value); if (int(end - start) < unit.length()) { if (unit.right(2) == "px") value = SvgUtil::fromUserSpace(value); else if (unit.right(2) == "pt") value = ptToPx(gc, value); else if (unit.right(2) == "cm") value = ptToPx(gc, CM_TO_POINT(value)); else if (unit.right(2) == "pc") value = ptToPx(gc, PI_TO_POINT(value)); else if (unit.right(2) == "mm") value = ptToPx(gc, MM_TO_POINT(value)); else if (unit.right(2) == "in") value = ptToPx(gc, INCH_TO_POINT(value)); else if (unit.right(2) == "em") // NOTE: all the fonts should be created with 'pt' size, not px! value = ptToPx(gc, value * gc->font.pointSize()); else if (unit.right(2) == "ex") { QFontMetrics metrics(gc->font); value = ptToPx(gc, value * metrics.xHeight()); } else if (unit.right(1) == "%") { if (horiz && vert) value = (value / 100.0) * (sqrt(pow(bbox.width(), 2) + pow(bbox.height(), 2)) / sqrt(2.0)); else if (horiz) value = (value / 100.0) * bbox.width(); else if (vert) value = (value / 100.0) * bbox.height(); } } else { value = SvgUtil::fromUserSpace(value); } /*else { if( m_gc.top() ) { if( horiz && vert ) value *= sqrt( pow( m_gc.top()->matrix.m11(), 2 ) + pow( m_gc.top()->matrix.m22(), 2 ) ) / sqrt( 2.0 ); else if( horiz ) value /= m_gc.top()->matrix.m11(); else if( vert ) value /= m_gc.top()->matrix.m22(); } }*/ //value *= 90.0 / DPI; return value; } qreal SvgUtil::parseUnitX(SvgGraphicsContext *gc, const QString &unit) { if (gc->forcePercentage) { return SvgUtil::fromPercentage(unit) * gc->currentBoundingBox.width(); } else { return SvgUtil::parseUnit(gc, unit, true, false, gc->currentBoundingBox); } } qreal SvgUtil::parseUnitY(SvgGraphicsContext *gc, const QString &unit) { if (gc->forcePercentage) { return SvgUtil::fromPercentage(unit) * gc->currentBoundingBox.height(); } else { return SvgUtil::parseUnit(gc, unit, false, true, gc->currentBoundingBox); } } qreal SvgUtil::parseUnitXY(SvgGraphicsContext *gc, const QString &unit) { if (gc->forcePercentage) { const qreal value = SvgUtil::fromPercentage(unit); return value * sqrt(pow(gc->currentBoundingBox.width(), 2) + pow(gc->currentBoundingBox.height(), 2)) / sqrt(2.0); } else { return SvgUtil::parseUnit(gc, unit, true, true, gc->currentBoundingBox); } } qreal SvgUtil::parseUnitAngular(SvgGraphicsContext *gc, const QString &unit) { Q_UNUSED(gc); qreal value = 0.0; if (unit.isEmpty()) return value; QByteArray unitLatin1 = unit.toLower().toLatin1(); const char *start = unitLatin1.data(); if (!start) return value; const char *end = parseNumber(start, value); if (int(end - start) < unit.length()) { if (unit.right(3) == "deg") { value = kisDegreesToRadians(value); } else if (unit.right(4) == "grad") { value *= M_PI / 200; } else if (unit.right(3) == "rad") { // noop! } else { value = kisDegreesToRadians(value); } } else { value = kisDegreesToRadians(value); } return value; } qreal SvgUtil::parseNumber(const QString &string) { qreal value = 0.0; if (string.isEmpty()) return value; QByteArray unitLatin1 = string.toLatin1(); const char *start = unitLatin1.data(); if (!start) return value; const char *end = parseNumber(start, value); KIS_SAFE_ASSERT_RECOVER_NOOP(int(end - start) == string.length()); return value; } const char * SvgUtil::parseNumber(const char *ptr, qreal &number) { int integer, exponent; qreal decimal, frac; int sign, expsign; exponent = 0; integer = 0; frac = 1.0; decimal = 0; sign = 1; expsign = 1; // read the sign if (*ptr == '+') { ptr++; } else if (*ptr == '-') { ptr++; sign = -1; } // read the integer part while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') integer = (integer * 10) + *(ptr++) - '0'; if (*ptr == '.') { // read the decimals ptr++; while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') decimal += (*(ptr++) - '0') * (frac *= 0.1); } if (*ptr == 'e' || *ptr == 'E') { // read the exponent part ptr++; // read the sign of the exponent if (*ptr == '+') { ptr++; } else if (*ptr == '-') { ptr++; expsign = -1; } exponent = 0; while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') { exponent *= 10; exponent += *ptr - '0'; ptr++; } } number = integer + decimal; number *= sign * pow((double)10, double(expsign * exponent)); return ptr; } QString SvgUtil::mapExtendedShapeTag(const QString &tagName, const KoXmlElement &element) { QString result = tagName; if (tagName == "path") { QString kritaType = element.attribute("krita:type", ""); QString sodipodiType = element.attribute("sodipodi:type", ""); if (kritaType == "arc") { result = "krita:arc"; } else if (sodipodiType == "arc") { result = "sodipodi:arc"; } } return result; } QStringList SvgUtil::simplifyList(const QString &str) { QString attribute = str; attribute.replace(',', ' '); attribute.remove('\r'); attribute.remove('\n'); return attribute.simplified().split(' ', QString::SkipEmptyParts); } SvgUtil::PreserveAspectRatioParser::PreserveAspectRatioParser(const QString &str) { QRegExp rexp("(defer)?\\s*(none|(x(Min|Max|Mid)Y(Min|Max|Mid)))\\s*(meet|slice)?", Qt::CaseInsensitive); int index = rexp.indexIn(str.toLower()); if (index >= 0) { if (rexp.cap(1) == "defer") { defer = true; } if (rexp.cap(2) != "none") { xAlignment = alignmentFromString(rexp.cap(4)); yAlignment = alignmentFromString(rexp.cap(5)); mode = rexp.cap(6) == "slice" ? Qt::KeepAspectRatioByExpanding : Qt::KeepAspectRatio; } } } QPointF SvgUtil::PreserveAspectRatioParser::rectAnchorPoint(const QRectF &rc) const { return QPointF(alignedValue(rc.x(), rc.x() + rc.width(), xAlignment), alignedValue(rc.y(), rc.y() + rc.height(), yAlignment)); } QString SvgUtil::PreserveAspectRatioParser::toString() const { QString result; if (!defer && xAlignment == Middle && yAlignment == Middle && mode == Qt::KeepAspectRatio) { return result; } if (defer) { result += "defer "; } if (mode == Qt::IgnoreAspectRatio) { result += "none"; } else { result += QString("x%1Y%2") .arg(alignmentToString(xAlignment)) .arg(alignmentToString(yAlignment)); if (mode == Qt::KeepAspectRatioByExpanding) { result += " slice"; } } return result; } SvgUtil::PreserveAspectRatioParser::Alignment SvgUtil::PreserveAspectRatioParser::alignmentFromString(const QString &str) const { return str == "max" ? Max : str == "mid" ? Middle : Min; } QString SvgUtil::PreserveAspectRatioParser::alignmentToString(SvgUtil::PreserveAspectRatioParser::Alignment alignment) const { return alignment == Max ? "Max" : alignment == Min ? "Min" : "Mid"; } qreal SvgUtil::PreserveAspectRatioParser::alignedValue(qreal min, qreal max, SvgUtil::PreserveAspectRatioParser::Alignment alignment) { qreal result = min; switch (alignment) { case Min: result = min; break; case Middle: result = 0.5 * (min + max); break; case Max: result = max; break; } return result; } diff --git a/libs/flake/svg/SvgWriter.cpp b/libs/flake/svg/SvgWriter.cpp index 2e139c5bfd..eccbe1134a 100644 --- a/libs/flake/svg/SvgWriter.cpp +++ b/libs/flake/svg/SvgWriter.cpp @@ -1,321 +1,321 @@ /* This file is part of the KDE project Copyright (C) 2002 Lars Siebold Copyright (C) 2002-2003,2005 Rob Buis Copyright (C) 2002,2005-2006 David Faure Copyright (C) 2002 Werner Trobin Copyright (C) 2002 Lennart Kudling Copyright (C) 2004 Nicolas Goutte Copyright (C) 2005 Boudewijn Rempt Copyright (C) 2005 Raphael Langerhorst Copyright (C) 2005 Thomas Zander Copyright (C) 2005,2007-2008 Jan Hambrecht Copyright (C) 2006 Inge Wallin Copyright (C) 2006 Martin Pfeiffer Copyright (C) 2006 Gábor Lehel Copyright (C) 2006 Laurent Montel Copyright (C) 2006 Christian Mueller Copyright (C) 2006 Ariya Hidayat Copyright (C) 2010 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 "SvgWriter.h" #include "SvgUtil.h" #include "SvgSavingContext.h" #include "SvgShape.h" #include "SvgStyleWriter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include SvgWriter::SvgWriter(const QList &layers) : m_writeInlineImages(true) { Q_FOREACH (KoShapeLayer *layer, layers) m_toplevelShapes.append(layer); } SvgWriter::SvgWriter(const QList &toplevelShapes) : m_toplevelShapes(toplevelShapes) , m_writeInlineImages(true) { } SvgWriter::~SvgWriter() { } bool SvgWriter::save(const QString &filename, const QSizeF &pageSize, bool writeInlineImages) { QFile fileOut(filename); if (!fileOut.open(QIODevice::WriteOnly)) return false; m_writeInlineImages = writeInlineImages; const bool success = save(fileOut, pageSize); m_writeInlineImages = true; fileOut.close(); return success; } bool SvgWriter::save(QIODevice &outputDevice, const QSizeF &pageSize) { if (m_toplevelShapes.isEmpty()) { return false; } QTextStream svgStream(&outputDevice); svgStream.setCodec("UTF-8"); // standard header: svgStream << "" << endl; svgStream << "" << endl; // add some PR. one line is more than enough. - svgStream << "" << endl; + svgStream << "" << endl; svgStream << "" << endl; if (!m_documentTitle.isNull() && !m_documentTitle.isEmpty()) { svgStream << "" << m_documentTitle << "" << endl; } if (!m_documentDescription.isNull() && !m_documentDescription.isEmpty()) { svgStream << "" << m_documentDescription << "" << endl; } { SvgSavingContext savingContext(outputDevice, m_writeInlineImages); saveShapes(m_toplevelShapes, savingContext); } // end tag: svgStream << endl << "" << endl; return true; } bool SvgWriter::saveDetached(QIODevice &outputDevice) { if (m_toplevelShapes.isEmpty()) return false; SvgSavingContext savingContext(outputDevice, m_writeInlineImages); saveShapes(m_toplevelShapes, savingContext); return true; } bool SvgWriter::saveDetached(SvgSavingContext &savingContext) { if (m_toplevelShapes.isEmpty()) return false; saveShapes(m_toplevelShapes, savingContext); return true; } void SvgWriter::saveShapes(const QList shapes, SvgSavingContext &savingContext) { // top level shapes Q_FOREACH (KoShape *shape, shapes) { KoShapeLayer *layer = dynamic_cast(shape); if(layer) { saveLayer(layer, savingContext); } else { KoShapeGroup *group = dynamic_cast(shape); if (group) saveGroup(group, savingContext); else saveShape(shape, savingContext); } } } void SvgWriter::saveLayer(KoShapeLayer *layer, SvgSavingContext &context) { context.shapeWriter().startElement("g"); context.shapeWriter().addAttribute("id", context.getID(layer)); QList sortedShapes = layer->shapes(); std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape * shape, sortedShapes) { KoShapeGroup * group = dynamic_cast(shape); if (group) saveGroup(group, context); else saveShape(shape, context); } context.shapeWriter().endElement(); } void SvgWriter::saveGroup(KoShapeGroup * group, SvgSavingContext &context) { context.shapeWriter().startElement("g"); context.shapeWriter().addAttribute("id", context.getID(group)); SvgUtil::writeTransformAttributeLazy("transform", group->transformation(), context.shapeWriter()); SvgStyleWriter::saveSvgStyle(group, context); QList sortedShapes = group->shapes(); std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape * shape, sortedShapes) { KoShapeGroup * childGroup = dynamic_cast(shape); if (childGroup) saveGroup(childGroup, context); else saveShape(shape, context); } context.shapeWriter().endElement(); } void SvgWriter::saveShape(KoShape *shape, SvgSavingContext &context) { SvgShape *svgShape = dynamic_cast(shape); if (svgShape && svgShape->saveSvg(context)) return; KoPathShape * path = dynamic_cast(shape); if (path) { savePath(path, context); } else { // generic saving of shape via a switch element saveGeneric(shape, context); } } void SvgWriter::savePath(KoPathShape *path, SvgSavingContext &context) { context.shapeWriter().startElement("path"); context.shapeWriter().addAttribute("id", context.getID(path)); SvgUtil::writeTransformAttributeLazy("transform", path->transformation(), context.shapeWriter()); SvgStyleWriter::saveSvgStyle(path, context); context.shapeWriter().addAttribute("d", path->toString(context.userSpaceTransform())); context.shapeWriter().endElement(); } void SvgWriter::saveGeneric(KoShape *shape, SvgSavingContext &context) { KIS_SAFE_ASSERT_RECOVER_RETURN(shape); const QRectF bbox = shape->boundingRect(); // paint shape to the image KoShapePainter painter; painter.setShapes(QList()<< shape); // generate svg from shape QBuffer svgBuffer; QSvgGenerator svgGenerator; svgGenerator.setOutputDevice(&svgBuffer); /** * HACK ALERT: Qt (and Krita 3.x) has a weird bug, it assumes that all font sizes are * defined in 96 ppi resolution, even though your the resolution in QSvgGenerator * is manually set to 72 ppi. So here we do a tricky thing: we set a fake resolution * to (72 * 72 / 96) = 54 ppi, which guarantees that the text, when painted in 96 ppi, * will be actually painted in 72 ppi. * * BUG: 389802 */ if (shape->shapeId() == "TextShapeID") { svgGenerator.setResolution(54); } QPainter svgPainter; svgPainter.begin(&svgGenerator); painter.paint(svgPainter, SvgUtil::toUserSpace(bbox).toRect(), bbox); svgPainter.end(); // remove anything before the start of the svg element from the buffer int startOfContent = svgBuffer.buffer().indexOf("0) { svgBuffer.buffer().remove(0, startOfContent); } // check if painting to svg produced any output if (svgBuffer.buffer().isEmpty()) { // prepare a transparent image, make it twice as big as the original size QImage image(2*bbox.size().toSize(), QImage::Format_ARGB32); image.fill(0); painter.paint(image); context.shapeWriter().startElement("image"); context.shapeWriter().addAttribute("id", context.getID(shape)); context.shapeWriter().addAttribute("x", bbox.x()); context.shapeWriter().addAttribute("y", bbox.y()); context.shapeWriter().addAttribute("width", bbox.width()); context.shapeWriter().addAttribute("height", bbox.height()); context.shapeWriter().addAttribute("xlink:href", context.saveImage(image)); context.shapeWriter().endElement(); // image } else { context.shapeWriter().addCompleteElement(&svgBuffer); } // TODO: once we support saving single (flat) odf files // we can embed these here to have full support for generic shapes } void SvgWriter::setDocumentTitle(QString title) { m_documentTitle = title; } void SvgWriter::setDocumentDescription(QString description) { m_documentDescription = description; } diff --git a/libs/flake/tests/CMakeLists.txt b/libs/flake/tests/CMakeLists.txt index f2a07a9033..8aa8bb3303 100644 --- a/libs/flake/tests/CMakeLists.txt +++ b/libs/flake/tests/CMakeLists.txt @@ -1,89 +1,86 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include(ECMAddTests) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() ecm_add_tests( TestPosition.cpp TestSelection.cpp TestPathTool.cpp TestShapeAt.cpp TestShapePainting.cpp TestKoShapeFactory.cpp TestKoShapeRegistry.cpp TestShapeContainer.cpp TestShapeGroupCommand.cpp TestShapeReorderCommand.cpp TestImageCollection.cpp TestResourceManager.cpp TestShapeBackgroundCommand.cpp TestShapeStrokeCommand.cpp TestShapeShadowCommand.cpp TestInputDevice.cpp TestSnapStrategy.cpp TestPathShape.cpp TestControlPointMoveCommand.cpp TestPointTypeCommand.cpp TestPointRemoveCommand.cpp TestRemoveSubpathCommand.cpp TestPathSegment.cpp TestSegmentTypeCommand.cpp TestKoDrag.cpp TestKoMarkerCollection.cpp LINK_LIBRARIES kritaflake Qt5::Test NAME_PREFIX "libs-flake-") ecm_add_test( TestSvgParser.cpp TEST_NAME TestSvgParser LINK_LIBRARIES kritaflake Qt5::Test NAME_PREFIX "libs-flake-") ecm_add_test( TestSvgParser.cpp TEST_NAME TestSvgParserCloned LINK_LIBRARIES kritaflake Qt5::Test NAME_PREFIX "libs-flake-") set_property(TARGET TestSvgParserCloned PROPERTY COMPILE_DEFINITIONS USE_CLONED_SHAPES) ecm_add_test( TestSvgParser.cpp TEST_NAME TestSvgParserRoundTrip LINK_LIBRARIES kritaflake Qt5::Test NAME_PREFIX "libs-flake-") set_property(TARGET TestSvgParserRoundTrip PROPERTY COMPILE_DEFINITIONS USE_ROUND_TRIP) ############## broken tests ############### krita_add_broken_unit_test(TestPointMergeCommand.cpp TEST_NAME TestPointMergeCommand LINK_LIBRARIES kritaflake Qt5::Test NAME_PREFIX "libs-flake-") -krita_add_broken_unit_test( - TestSvgText.cpp +krita_add_broken_unit_test( TestSvgText.cpp TEST_NAME TestSvgText LINK_LIBRARIES kritaflake Qt5::Test NAME_PREFIX "libs-flake-") -krita_add_broken_unit_test( - TestSvgText.cpp +krita_add_broken_unit_test( TestSvgText.cpp TEST_NAME TestSvgTextCloned LINK_LIBRARIES kritaflake Qt5::Test NAME_PREFIX "libs-flake-") set_property(TARGET TestSvgTextCloned PROPERTY COMPILE_DEFINITIONS USE_CLONED_SHAPES) -krita_add_broken_unit_test( - TestSvgText.cpp +krita_add_broken_unit_test( TestSvgText.cpp TEST_NAME TestSvgTextRoundTrip LINK_LIBRARIES kritaflake Qt5::Test NAME_PREFIX "libs-flake-") set_property(TARGET TestSvgTextRoundTrip PROPERTY COMPILE_DEFINITIONS USE_ROUND_TRIP) diff --git a/libs/flake/text/KoSvgTextShapeMarkupConverter.h b/libs/flake/text/KoSvgTextShapeMarkupConverter.h index 9b58ce2468..c57c21fa5b 100644 --- a/libs/flake/text/KoSvgTextShapeMarkupConverter.h +++ b/libs/flake/text/KoSvgTextShapeMarkupConverter.h @@ -1,144 +1,144 @@ /* * 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 KOSVGTEXTSHAPEMARKUPCONVERTER_H #define KOSVGTEXTSHAPEMARKUPCONVERTER_H #include "kritaflake_export.h" #include #include #include #include class QRectF; class KoSvgTextShape; /** * KoSvgTextShapeMarkupConverter is a utility class for converting a * KoSvgTextShape to/from user-editable markup/svg representation. * * Please note that the converted SVG is **not** the same as when saved into * .kra! Some attributes are dropped to make the editing is easier for the * user. */ class KRITAFLAKE_EXPORT KoSvgTextShapeMarkupConverter { public: KoSvgTextShapeMarkupConverter(KoSvgTextShape *shape); ~KoSvgTextShapeMarkupConverter(); /** * Convert the text shape into two strings: text and styles. Styles string * is non-empty only when the text has some gradient/pattern attached. It is * intended to be places into a separate tab in the GUI. * * @return true on success */ bool convertToSvg(QString *svgText, QString *stylesText); /** * @brief upload the svg representation of text into the shape * @param svgText \ part of SVG * @param stylesText \ part of SVG (used only for gradients and patterns) * @param boundsInPixels bounds of the entire image in pixel. Used for parsing percentage units. * @param pixelsPerInch resolution of the image where we load the shape to * * @return true if the text was parsed successfully. Check `errors()` and `warnings()` for details. */ bool convertFromSvg(const QString &svgText, const QString &stylesText, const QRectF &boundsInPixels, qreal pixelsPerInch); /** * @brief convertToHtml convert the text in the text shape to html * @param htmlText will be filled with correct html representing the text in the shape * @return @c true on success */ bool convertToHtml(QString *htmlText); /** - * @brief convertFromHtml converted Qt rich text html (and no other: http://doc.qt.io/qt-5/richtext-html-subset.html) to SVG + * @brief convertFromHtml converted Qt rich text html (and no other: https://doc.qt.io/qt-5/richtext-html-subset.html) to SVG * @param htmlText the input html * @param svgText the converted svg text element * @param styles * @return @c true if the conversion was successful */ bool convertFromHtml(const QString &htmlText, QString *svgText, QString *styles); /** * @brief convertDocumentToSvg * @param doc the QTextDocument to convert. * @param svgText the converted svg text element * @return @c true if the conversion was successful */ bool convertDocumentToSvg(const QTextDocument *doc, QString *svgText); /** * @brief convertSvgToDocument * @param svgText the \ element and it's children as a string. * @param doc the QTextDocument that the conversion is written to. * @return @c true if the conversion was successful */ bool convertSvgToDocument(const QString &svgText, QTextDocument *doc); /** * A list of errors happened during loading the user's text */ QStringList errors() const; /** * A list of warnings produced during loading the user's text */ QStringList warnings() const; /** * @brief style * creates a style string based on the blockformat and the format. * @param format the textCharFormat of the current text. * @param blockFormat the block format of the current text. * @param mostCommon the most common format to compare the format to. * @return a string that can be written into a style element. */ QString style(QTextCharFormat format, QTextBlockFormat blockFormat, QTextCharFormat mostCommon = QTextCharFormat()); /** * @brief stylesFromString * returns a qvector with two textformats: * at 0 is the QTextCharFormat * at 1 is the QTextBlockFormat * @param styles a style string split at ";" * @param currentCharFormat the current charformat to compare against. * @param currentBlockFormat the current blockformat to compare against. * @return A QVector with at 0 a QTextCharFormat and at 1 a QBlockCharFormat. */ static QVector stylesFromString(QStringList styles, QTextCharFormat currentCharFormat, QTextBlockFormat currentBlockFormat); /** * @brief formatDifference * A class to get the difference between two text-char formats. * @param test the format to test * @param reference the format to test against. * @return the difference between the two. */ QTextFormat formatDifference(QTextFormat test, QTextFormat reference); private: struct Private; const QScopedPointer d; }; #endif // KOSVGTEXTSHAPEMARKUPCONVERTER_H diff --git a/libs/global/KisUsageLogger.cpp b/libs/global/KisUsageLogger.cpp index fdce7c3901..682ca524ec 100644 --- a/libs/global/KisUsageLogger.cpp +++ b/libs/global/KisUsageLogger.cpp @@ -1,206 +1,250 @@ /* * 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 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()); + 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::writeSectionHeader() +void KisUsageLogger::writeSysInfo(const QString &message) { - s_instance->d->logFile.write(s_sectionHeader.toUtf8()); + 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->logFile.isOpen()); + 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(' ')); - QString disclaimer = i18n("WARNING: This file contains information about your system and the\n" - "images you have been working with.\n" - "\n" - "If you have problems with Krita, the Krita developers might ask\n" - "you to share this file with them. The information in this file is\n" - "not shared automatically with the Krita developers in any way. You\n" - "can disable logging to this file in Krita's Configure Krita Dialog.\n" - "\n" - "Please review the contents of this file before sharing this file with\n" - "anyone.\n\n"); - - QString systemInfo; - - // NOTE: This is intentionally not translated! - - // Krita version info - systemInfo.append("Krita\n"); - systemInfo.append("\n Version: ").append(KritaVersionWrapper::versionString(true)); - systemInfo.append("\n Languages: ").append(KLocalizedString::languages().join(", ")); - systemInfo.append("\n Hidpi: ").append(QCoreApplication::testAttribute(Qt::AA_EnableHighDpiScaling) ? "true" : "false"); - systemInfo.append("\n\n"); - - systemInfo.append("Qt\n"); - systemInfo.append("\n Version (compiled): ").append(QT_VERSION_STR); - systemInfo.append("\n Version (loaded): ").append(qVersion()); - systemInfo.append("\n\n"); - - // OS information - systemInfo.append("OS Information\n"); - systemInfo.append("\n Build ABI: ").append(QSysInfo::buildAbi()); - systemInfo.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture()); - systemInfo.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture()); - systemInfo.append("\n Kernel Type: ").append(QSysInfo::kernelType()); - systemInfo.append("\n Kernel Version: ").append(QSysInfo::kernelVersion()); - systemInfo.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName()); - systemInfo.append("\n Product Type: ").append(QSysInfo::productType()); - systemInfo.append("\n Product Version: ").append(QSysInfo::productVersion()); - systemInfo.append("\n\n"); - - writeSectionHeader(); s_instance->d->logFile.write(sessionHeader.toUtf8()); - s_instance->d->logFile.write(disclaimer.toUtf8()); - s_instance->d->logFile.write(systemInfo.toUtf8()); + 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(); +} + +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("\n").last().contains("CLOSING SESSION")) { + 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/KisUsageLogger.h b/libs/global/KisUsageLogger.h index 34dd84755f..4ac7fc8ded 100644 --- a/libs/global/KisUsageLogger.h +++ b/libs/global/KisUsageLogger.h @@ -1,64 +1,73 @@ /* * 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. */ #ifndef KISUSAGELOGGER_H #define KISUSAGELOGGER_H #include #include #include "kritaglobal_export.h" /** * @brief The KisUsageLogger class logs messages to a logfile */ class KRITAGLOBAL_EXPORT KisUsageLogger { public: KisUsageLogger(); ~KisUsageLogger(); static void initialize(); static void close(); + /// basic system information + /// (there is other information spreaded in the code + /// check usages of writeSysInfo for details) + static QString basicSystemInfo(); + /// Logs with date/time static void log(const QString &message); /// Writes without date/time static void write(const QString &message); - static void writeSectionHeader(); + /// Writes to the system information file and Krita log + static void writeSysInfo(const QString &message); static void writeHeader(); + /// Returns information about all available screens + static QString screenInformation(); + private: void rotateLog(); Q_DISABLE_COPY(KisUsageLogger) struct Private; const QScopedPointer d; static const QString s_sectionHeader; static const int s_maxLogs {10}; }; #endif // KISUSAGELOGGER_H diff --git a/libs/global/kis_shared_ptr.h b/libs/global/kis_shared_ptr.h index b9f1c93602..833d2128ca 100644 --- a/libs/global/kis_shared_ptr.h +++ b/libs/global/kis_shared_ptr.h @@ -1,526 +1,526 @@ /* * Copyright (c) 2005 Frerich Raabe * Copyright (c) 2006,2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_SHAREDPTR_H #define KIS_SHAREDPTR_H #include #include #include "kis_memory_leak_tracker.h" template class KisWeakSharedPtr; /** * KisSharedPtr is a shared pointer similar to KSharedPtr and * boost::shared_ptr. The difference with KSharedPtr is that our * constructor is not explicit. * * A shared pointer is a wrapper around a real pointer. The shared * pointer keeps a reference count, and when the reference count drops * to 0 the contained pointer is deleted. You can use the shared * pointer just as you would use a real pointer. * * See also also item 28 and 29 of More Effective C++ and - * http://bugs.kde.org/show_bug.cgi?id=52261 as well as - * http://www.boost.org/libs/smart_ptr/shared_ptr.htm. + * https://bugs.kde.org/show_bug.cgi?id=52261 as well as + * https://www.boost.org/libs/smart_ptr/shared_ptr.htm. * * Advantage of KisSharedPtr over boost pointer or QSharedPointer? * * The difference with boost share pointer is that in * boost::shared_ptr, the counter is kept inside the smart pointer, * meaning that you should never never remove the pointer from its * smart pointer object, because if you do that, and somewhere in the * code, the pointer is put back in a smart pointer, then you have two * counters, and when one reach zero, then the object gets deleted * while some other code thinks the pointer is still valid. * * Disadvantage of KisSharedPtr compared to boost pointer? * * KisSharedPtr requires the class to inherits KisShared. * * Difference with QSharedPointer * * QSharedPointer and KisSharedPtr are very similar, but * QSharedPointer has an explicit constructor which makes it more * painful to use in some constructions. And QSharedPointer * doesn't offer a weak pointer. */ template class KisSharedPtr { friend class KisWeakSharedPtr; public: /** * Creates a null pointer. */ inline KisSharedPtr() : d(0) { } /** * Creates a new pointer. * @param p the pointer */ inline KisSharedPtr(T* p) : d(p) { ref(); } inline KisSharedPtr(const KisWeakSharedPtr& o); // Free the pointer and set it to new value void attach(T* p); // Free the pointer void clear(); /** * Copies a pointer. * @param o the pointer to copy */ inline KisSharedPtr(const KisSharedPtr& o) : d(o.d) { ref(); } /** * Dereferences the object that this pointer points to. If it was * the last reference, the object will be deleted. */ inline ~KisSharedPtr() { deref(); } inline KisSharedPtr& operator= (const KisSharedPtr& o) { attach(o.d); return *this; } inline bool operator== (const T* p) const { return (d == p); } inline bool operator!= (const T* p) const { return (d != p); } inline bool operator== (const KisSharedPtr& o) const { return (d == o.d); } inline bool operator!= (const KisSharedPtr& o) const { return (d != o.d); } inline KisSharedPtr& operator= (T* p) { attach(p); return *this; } inline operator const T*() const { return d; } template< class T2> inline operator KisSharedPtr() const { return KisSharedPtr(d); } /** * @return the contained pointer. If you delete the contained * pointer, you will make KisSharedPtr very unhappy. It is * perfectly save to put the contained pointer in another * KisSharedPtr, though. */ inline T* data() { return d; } /** * @return the pointer */ inline const T* data() const { return d; } /** * @return a const pointer to the shared object. */ inline const T* constData() const { return d; } inline const T& operator*() const { Q_ASSERT(d); return *d; } inline T& operator*() { Q_ASSERT(d); return *d; } inline const T* operator->() const { Q_ASSERT(d); return d; } inline T* operator->() { Q_ASSERT(d); return d; } /** * @return true if the pointer is null */ inline bool isNull() const { return (d == 0); } inline static void ref(const KisSharedPtr* sp, T* t) { #ifndef HAVE_MEMORY_LEAK_TRACKER Q_UNUSED(sp); #else KisMemoryLeakTracker::instance()->reference(t, sp); #endif if (t) { t->ref(); } } inline static bool deref(const KisSharedPtr* sp, T* t) { #ifndef HAVE_MEMORY_LEAK_TRACKER Q_UNUSED(sp); #else KisMemoryLeakTracker::instance()->dereference(t, sp); #endif if (t && !t->deref()) { delete t; return false; } return true; } private: inline void ref() const { ref(this, d); } inline void deref() const { bool v = deref(this, d); #ifndef NDEBUG if (!v) { d = 0; } #else Q_UNUSED(v); #endif } private: mutable T* d; }; /** * A weak shared ptr is an ordinary shared ptr, with two differences: * it doesn't delete the contained pointer if the refcount drops to * zero and it doesn't prevent the contained pointer from being * deleted if the last strong shared pointer goes out of scope. */ template class KisWeakSharedPtr { friend class KisSharedPtr; public: /** * Creates a null pointer. */ inline KisWeakSharedPtr() : d(0), weakReference(0) { } /** * Creates a new pointer. * @param p the pointer */ inline KisWeakSharedPtr(T* p) { load(p); } inline KisWeakSharedPtr(const KisSharedPtr& o) { load(o.d); } /** * Copies a pointer. * @param o the pointer to copy */ inline KisWeakSharedPtr(const KisWeakSharedPtr& o) { if (o.isConsistent()) { load(o.d); } else { d = 0; weakReference = 0; } } inline ~KisWeakSharedPtr() { detach(); } inline KisWeakSharedPtr& operator= (const KisWeakSharedPtr& o) { attach(o); return *this; } inline bool operator== (const T* p) const { return (d == p); } inline bool operator!= (const T* p) const { return (d != p); } inline bool operator== (const KisWeakSharedPtr& o) const { return (d == o.d); } inline bool operator!= (const KisWeakSharedPtr& o) const { return (d != o.d); } inline KisWeakSharedPtr& operator= (T* p) { attach(p); return *this; } template< class T2> inline operator KisWeakSharedPtr() const { return KisWeakSharedPtr(d); } /** * Note that if you use this function, the pointer might be destroyed * if KisSharedPtr pointing to this pointer are deleted, resulting in * a segmentation fault. Use with care. * @return a const pointer to the shared object. */ inline T* data() { if (!isConsistent()) { warnKrita << kisBacktrace(); Q_ASSERT_X(0, "KisWeakSharedPtr", "Weak pointer is not valid!"); } return d; } /** * @see data() */ inline const T* data() const { if (!isConsistent()) { warnKrita << kisBacktrace(); Q_ASSERT_X(0, "KisWeakSharedPtr", "Weak pointer is not valid!"); } return d; } /** * @see data() */ inline const T* constData() const { if (!isConsistent()) { warnKrita << kisBacktrace(); Q_ASSERT_X(0, "KisWeakSharedPtr", "Weak pointer is not valid!"); } return d; } /** * @see data() */ inline operator const T*() const { /** * This operator is used in boolean expressions where we check * for pointer consistency, so return 0 instead of asserting. */ return isConsistent() ? d : 0; } inline const T& operator*() const { if (!isValid()) { warnKrita << kisBacktrace(); Q_ASSERT_X(0, "KisWeakSharedPtr", "Weak pointer is not valid!"); } return *d; } inline T& operator*() { if (!isValid()) { warnKrita << kisBacktrace(); Q_ASSERT_X(0, "KisWeakSharedPtr", "Weak pointer is not valid!"); } return *d; } inline const T* operator->() const { if (!isValid()) { warnKrita << kisBacktrace(); Q_ASSERT_X(0, "KisWeakSharedPtr", "Weak pointer is not valid!"); } return d; } inline T* operator->() { if (!isValid()) { warnKrita << kisBacktrace(); Q_ASSERT_X(0, "KisWeakSharedPtr", "Weak pointer is not valid!"); } return d; } /** * @return true if the pointer is null */ inline bool isNull() const { return (d == 0); } /** * @return true if the weak pointer points to a valid pointer * and false if the data has been deleted or is null */ inline bool isValid() const { Q_ASSERT(!d || weakReference); return d && weakReference && isOdd((int)*weakReference); } /** * @brief toStrongRef returns a KisSharedPtr which may be dereferenced. * * Weak pointers should only be used to track ownership but never be used as pointers. * This has historically not been the case, but in new API this function should be used * instead of directly using a weak pointer as pointer. * @return a KisSharedPtr, which may be null */ inline KisSharedPtr toStrongRef() const { return KisSharedPtr(*this); } private: static const qint32 WEAK_REF = 2; static inline bool isOdd(const qint32 &x) { return x & 0x01; } inline bool isConsistent() const { Q_ASSERT(!d || weakReference); return !d || (weakReference && isOdd((int)*weakReference)); } void load(T* newValue) { d = newValue; if (d) { weakReference = d->sharedWeakReference(); weakReference->fetchAndAddOrdered(WEAK_REF); } else { weakReference = 0; } } // see note in kis_shared.cc inline void attach(T* newValue) { detach(); load(newValue); } inline void attach(const KisWeakSharedPtr& o) { detach(); if (o.isConsistent()) { load(o.d); } else { d = 0; weakReference = 0; } } // see note in kis_shared.cc void detach() { d = 0; if (weakReference && weakReference->fetchAndAddOrdered(-WEAK_REF) <= WEAK_REF) { // sanity check: Q_ASSERT((int)*weakReference == 0); delete weakReference; weakReference = 0; } } mutable T* d; QAtomicInt *weakReference; }; template Q_INLINE_TEMPLATE KisSharedPtr::KisSharedPtr(const KisWeakSharedPtr& o) : d(o.d) { if (o.isValid()) { ref(); /** * Thread safety: * Is the object we have just referenced still valid? */ Q_ASSERT(o.isConsistent()); } else { d = 0; } } template Q_INLINE_TEMPLATE void KisSharedPtr::attach(T* p) { if (d != p) { ref(this, p); T* old = d; d = p; deref(this, old); } } template Q_INLINE_TEMPLATE void KisSharedPtr::clear() { attach((T*)0); } #endif diff --git a/libs/image/KisSelectionUpdateCompressor.cpp b/libs/image/KisSelectionUpdateCompressor.cpp index d9fd32e272..129e6d471b 100644 --- a/libs/image/KisSelectionUpdateCompressor.cpp +++ b/libs/image/KisSelectionUpdateCompressor.cpp @@ -1,76 +1,75 @@ /* * Copyright (c) 2018 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #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)), - m_hasStalledUpdate(false) + : 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; } 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/KisSelectionUpdateCompressor.h b/libs/image/KisSelectionUpdateCompressor.h index e003c3dd88..4ab98c5054 100644 --- a/libs/image/KisSelectionUpdateCompressor.h +++ b/libs/image/KisSelectionUpdateCompressor.h @@ -1,52 +1,52 @@ /* * Copyright (c) 2018 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISSELECTIONUPDATECOMPRESSOR_H #define KISSELECTIONUPDATECOMPRESSOR_H #include "kritaimage_export.h" #include "kis_thread_safe_signal_compressor.h" #include "kis_types.h" #include class KisSelectionUpdateCompressor : public QObject { Q_OBJECT public: KisSelectionUpdateCompressor(KisSelection *selection); ~KisSelectionUpdateCompressor(); public Q_SLOTS: void requestUpdate(const QRect &updateRect); void tryProcessStalledUpdate(); private Q_SLOTS: void startUpdateJob(); private: - KisSelection *m_parentSelection; - KisThreadSafeSignalCompressor *m_updateSignalCompressor; + KisSelection *m_parentSelection {0}; + KisThreadSafeSignalCompressor *m_updateSignalCompressor {0}; QRect m_updateRect; - bool m_fullUpdateRequested; + bool m_fullUpdateRequested {false}; - bool m_hasStalledUpdate; + bool m_hasStalledUpdate {false}; }; #endif // KISSELECTIONUPDATECOMPRESSOR_H diff --git a/libs/image/brushengine/kis_paintop.cc b/libs/image/brushengine/kis_paintop.cc index ac28205c37..c896dbb605 100644 --- a/libs/image/brushengine/kis_paintop.cc +++ b/libs/image/brushengine/kis_paintop.cc @@ -1,211 +1,211 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004,2007,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_paintop.h" -#include +#include #include #include #include #include "kis_painter.h" #include "kis_layer.h" #include "kis_image.h" #include "kis_paint_device.h" #include "kis_global.h" #include "kis_datamanager.h" #include #include #include "kis_vec.h" #include "kis_perspective_math.h" #include "kis_fixed_paint_device.h" #include "kis_paintop_utils.h" #define BEZIER_FLATNESS_THRESHOLD 0.5 #include #include struct Q_DECL_HIDDEN KisPaintOp::Private { Private(KisPaintOp *_q) : q(_q), dab(0), fanCornersEnabled(false), fanCornersStep(1.0) {} KisPaintOp *q; KisFixedPaintDeviceSP dab; KisPainter* painter; bool fanCornersEnabled; qreal fanCornersStep; }; KisPaintOp::KisPaintOp(KisPainter * painter) : d(new Private(this)) { d->painter = painter; } KisPaintOp::~KisPaintOp() { d->dab.clear(); delete d; } KisFixedPaintDeviceSP KisPaintOp::cachedDab() { return cachedDab(d->painter->device()->colorSpace()); } KisFixedPaintDeviceSP KisPaintOp::cachedDab(const KoColorSpace *cs) { if (!d->dab || *d->dab->colorSpace() != *cs) { d->dab = new KisFixedPaintDevice(cs); } return d->dab; } void KisPaintOp::setFanCornersInfo(bool fanCornersEnabled, qreal fanCornersStep) { d->fanCornersEnabled = fanCornersEnabled; d->fanCornersStep = fanCornersStep; } void KisPaintOp::splitCoordinate(qreal coordinate, qint32 *whole, qreal *fraction) { - const qint32 i = std::floor(coordinate); + const qint32 i = qFloor(coordinate); const qreal f = coordinate - i; *whole = i; *fraction = f; } std::pair KisPaintOp::doAsyncronousUpdate(QVector &jobs) { Q_UNUSED(jobs); return std::make_pair(40, false); } static void paintBezierCurve(KisPaintOp *paintOp, const KisPaintInformation &pi1, const KisVector2D &control1, const KisVector2D &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { LineEquation line = LineEquation::Through(toKisVector2D(pi1.pos()), toKisVector2D(pi2.pos())); qreal d1 = line.absDistance(control1); qreal d2 = line.absDistance(control2); if ((d1 < BEZIER_FLATNESS_THRESHOLD && d2 < BEZIER_FLATNESS_THRESHOLD) || qIsNaN(d1) || qIsNaN(d2)) { paintOp->paintLine(pi1, pi2, currentDistance); } else { // Midpoint subdivision. See Foley & Van Dam Computer Graphics P.508 KisVector2D l2 = (toKisVector2D(pi1.pos()) + control1) / 2; KisVector2D h = (control1 + control2) / 2; KisVector2D l3 = (l2 + h) / 2; KisVector2D r3 = (control2 + toKisVector2D(pi2.pos())) / 2; KisVector2D r2 = (h + r3) / 2; KisVector2D l4 = (l3 + r2) / 2; KisPaintInformation middlePI = KisPaintInformation::mix(toQPointF(l4), 0.5, pi1, pi2); paintBezierCurve(paintOp, pi1, l2, l3, middlePI, currentDistance); paintBezierCurve(paintOp, middlePI, r2, r3, pi2, currentDistance); } } void KisPaintOp::paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { return ::paintBezierCurve(this, pi1, toKisVector2D(control1), toKisVector2D(control2), pi2, currentDistance); } void KisPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { KisPaintOpUtils::paintLine(*this, pi1, pi2, currentDistance, d->fanCornersEnabled, d->fanCornersStep); } void KisPaintOp::paintAt(const KisPaintInformation& info, KisDistanceInformation *currentDistance) { Q_ASSERT(currentDistance); KisPaintInformation pi(info); pi.paintAt(*this, currentDistance); } void KisPaintOp::updateSpacing(const KisPaintInformation &info, KisDistanceInformation ¤tDistance) const { KisPaintInformation pi(info); KisSpacingInformation spacingInfo; { KisPaintInformation::DistanceInformationRegistrar r = pi.registerDistanceInformation(¤tDistance); spacingInfo = updateSpacingImpl(pi); } currentDistance.updateSpacing(spacingInfo); } void KisPaintOp::updateTiming(const KisPaintInformation &info, KisDistanceInformation ¤tDistance) const { KisPaintInformation pi(info); KisTimingInformation timingInfo; { KisPaintInformation::DistanceInformationRegistrar r = pi.registerDistanceInformation(¤tDistance); timingInfo = updateTimingImpl(pi); } currentDistance.updateTiming(timingInfo); } KisTimingInformation KisPaintOp::updateTimingImpl(const KisPaintInformation &info) const { Q_UNUSED(info); return KisTimingInformation(); } KisPainter* KisPaintOp::painter() const { return d->painter; } KisPaintDeviceSP KisPaintOp::source() const { return d->painter->device(); } diff --git a/libs/image/commands_new/kis_change_projection_color_command.cpp b/libs/image/commands_new/kis_change_projection_color_command.cpp index 5d4c0d8bbf..d32934af51 100644 --- a/libs/image/commands_new/kis_change_projection_color_command.cpp +++ b/libs/image/commands_new/kis_change_projection_color_command.cpp @@ -1,78 +1,78 @@ /* * 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_change_projection_color_command.h" #include "kis_image.h" #include "kis_image_animation_interface.h" KisChangeProjectionColorCommand::KisChangeProjectionColorCommand(KisImageSP image, const KoColor &newColor, KUndo2Command *parent) : KUndo2Command(kundo2_noi18n("CHANGE_PROJECTION_COLOR_COMMAND"), parent), m_image(image), m_oldColor(image->defaultProjectionColor()), m_newColor(newColor) { } KisChangeProjectionColorCommand::~KisChangeProjectionColorCommand() { } int KisChangeProjectionColorCommand::id() const { // we don't have a common commands id source in Krita yet, so // just use a random one ;) - // http://www.scientificamerican.com/article/most-popular-numbers-grapes-of-math/ + // https://www.scientificamerican.com/article/most-popular-numbers-grapes-of-math/ return 142857; } bool KisChangeProjectionColorCommand::mergeWith(const KUndo2Command* command) { const KisChangeProjectionColorCommand *other = dynamic_cast(command); if (!other || other->id() != id()) { return false; } m_newColor = other->m_newColor; return true; } void KisChangeProjectionColorCommand::redo() { KisImageSP image = m_image.toStrongRef(); if (!image) { return; } image->setDefaultProjectionColor(m_newColor); image->animationInterface()->setDefaultProjectionColor(m_newColor); } void KisChangeProjectionColorCommand::undo() { KisImageSP image = m_image.toStrongRef(); if (!image) { return; } image->setDefaultProjectionColor(m_oldColor); image->animationInterface()->setDefaultProjectionColor(m_oldColor); } diff --git a/libs/image/commands_new/kis_saved_commands.cpp b/libs/image/commands_new/kis_saved_commands.cpp index 3308ed5765..17c4065f1e 100644 --- a/libs/image/commands_new/kis_saved_commands.cpp +++ b/libs/image/commands_new/kis_saved_commands.cpp @@ -1,319 +1,314 @@ /* * 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_saved_commands.h" #include #include "kis_image_interfaces.h" #include "kis_stroke_strategy_undo_command_based.h" KisSavedCommandBase::KisSavedCommandBase(const KUndo2MagicString &name, KisStrokesFacade *strokesFacade) : KUndo2Command(name), m_strokesFacade(strokesFacade), m_skipOneRedo(true) { } KisSavedCommandBase::~KisSavedCommandBase() { } KisStrokesFacade* KisSavedCommandBase::strokesFacade() { return m_strokesFacade; } void KisSavedCommandBase::runStroke(bool undo) { KisStrokeStrategyUndoCommandBased *strategy = new KisStrokeStrategyUndoCommandBased(text(), undo, 0); strategy->setUsedWhileUndoRedo(true); KisStrokeId id = m_strokesFacade->startStroke(strategy); addCommands(id, undo); m_strokesFacade->endStroke(id); } void KisSavedCommandBase::undo() { runStroke(true); } void KisSavedCommandBase::redo() { /** * All the commands are first executed in the stroke and then * added to the undo stack. It means that the first redo should be * skipped */ if(m_skipOneRedo) { m_skipOneRedo = false; return; } runStroke(false); } KisSavedCommand::KisSavedCommand(KUndo2CommandSP command, KisStrokesFacade *strokesFacade) : KisSavedCommandBase(command->text(), strokesFacade), m_command(command) { } int KisSavedCommand::id() const { return m_command->id(); } bool KisSavedCommand::mergeWith(const KUndo2Command* command) { const KisSavedCommand *other = dynamic_cast(command); if (other) { command = other->m_command.data(); } return m_command->mergeWith(command); } void KisSavedCommand::addCommands(KisStrokeId id, bool undo) { strokesFacade()-> addJob(id, new KisStrokeStrategyUndoCommandBased::Data(m_command, undo)); } int KisSavedCommand::timedId() { return m_command->timedId(); } void KisSavedCommand::setTimedID(int timedID) { m_command->setTimedID(timedID); } bool KisSavedCommand::timedMergeWith(KUndo2Command *other) { return m_command->timedMergeWith(other); } QVector KisSavedCommand::mergeCommandsVector() { return m_command->mergeCommandsVector(); } void KisSavedCommand::setTime() { m_command->setTime(); } QTime KisSavedCommand::time() { return m_command->time(); } void KisSavedCommand::setEndTime() { m_command->setEndTime(); } QTime KisSavedCommand::endTime() { return m_command->endTime(); } bool KisSavedCommand::isMerged() { return m_command->isMerged(); } struct KisSavedMacroCommand::Private { struct SavedCommand { KUndo2CommandSP command; KisStrokeJobData::Sequentiality sequentiality; KisStrokeJobData::Exclusivity exclusivity; }; QVector commands; int macroId = -1; const KisSavedMacroCommand *overriddenCommand = 0; QVector skipWhenOverride; }; KisSavedMacroCommand::KisSavedMacroCommand(const KUndo2MagicString &name, KisStrokesFacade *strokesFacade) : KisSavedCommandBase(name, strokesFacade), m_d(new Private()) { } KisSavedMacroCommand::~KisSavedMacroCommand() { delete m_d; } void KisSavedMacroCommand::setMacroId(int value) { m_d->macroId = value; } int KisSavedMacroCommand::id() const { return m_d->macroId; } bool KisSavedMacroCommand::mergeWith(const KUndo2Command* command) { const KisSavedMacroCommand *other = dynamic_cast(command); if (!other || other->id() != id() || id() < 0 || other->id() < 0) return false; QVector &otherCommands = other->m_d->commands; if (other->m_d->overriddenCommand == this) { m_d->commands.clear(); Q_FOREACH (Private::SavedCommand cmd, other->m_d->commands) { if (!other->m_d->skipWhenOverride.contains(cmd.command.data())) { m_d->commands.append(cmd); } } if (other->extraData()) { setExtraData(other->extraData()->clone()); } else { setExtraData(0); } return true; } if (m_d->commands.size() != otherCommands.size()) return false; auto it = m_d->commands.constBegin(); auto end = m_d->commands.constEnd(); auto otherIt = otherCommands.constBegin(); auto otherEnd = otherCommands.constEnd(); bool sameCommands = true; while (it != end && otherIt != otherEnd) { if (it->command->id() < 0 || otherIt->command->id() < 0 || it->command->id() != otherIt->command->id() || it->sequentiality != otherIt->sequentiality || it->exclusivity != otherIt->exclusivity) { sameCommands = false; break; } ++it; ++otherIt; } if (!sameCommands) return false; it = m_d->commands.constBegin(); otherIt = otherCommands.constBegin(); while (it != end && otherIt != otherEnd) { if (it->command->id() != -1) { bool result = it->command->mergeWith(otherIt->command.data()); KIS_ASSERT_RECOVER(result) { return false; } } ++it; ++otherIt; } if (other->extraData()) { setExtraData(other->extraData()->clone()); } else { setExtraData(0); } return true; } void KisSavedMacroCommand::addCommand(KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity) { Private::SavedCommand item; item.command = command; item.sequentiality = sequentiality; item.exclusivity = exclusivity; m_d->commands.append(item); } -void KisSavedMacroCommand::performCancel(KisStrokeId id, bool strokeUndo) -{ - addCommands(id, !strokeUndo); -} - void KisSavedMacroCommand::getCommandExecutionJobs(QVector *jobs, bool undo, bool shouldGoToHistory) const { QVector::iterator it; if(!undo) { for(it = m_d->commands.begin(); it != m_d->commands.end(); it++) { *jobs << new KisStrokeStrategyUndoCommandBased:: Data(it->command, undo, it->sequentiality, it->exclusivity, shouldGoToHistory); } } else { for(it = m_d->commands.end(); it != m_d->commands.begin();) { --it; *jobs << new KisStrokeStrategyUndoCommandBased:: Data(it->command, undo, it->sequentiality, it->exclusivity, shouldGoToHistory); } } } void KisSavedMacroCommand::setOverrideInfo(const KisSavedMacroCommand *overriddenCommand, const QVector &skipWhileOverride) { m_d->overriddenCommand = overriddenCommand; m_d->skipWhenOverride = skipWhileOverride; } void KisSavedMacroCommand::addCommands(KisStrokeId id, bool undo) { QVector jobs; getCommandExecutionJobs(&jobs, undo); Q_FOREACH (KisStrokeJobData *job, jobs) { strokesFacade()->addJob(id, job); } } diff --git a/libs/image/commands_new/kis_saved_commands.h b/libs/image/commands_new/kis_saved_commands.h index 4f23de43fc..021e4f96d2 100644 --- a/libs/image/commands_new/kis_saved_commands.h +++ b/libs/image/commands_new/kis_saved_commands.h @@ -1,105 +1,103 @@ /* * 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_SAVED_COMMANDS_H #define __KIS_SAVED_COMMANDS_H #include #include "kis_types.h" #include "kis_stroke_job_strategy.h" class KisStrokesFacade; class KRITAIMAGE_EXPORT KisSavedCommandBase : public KUndo2Command { public: KisSavedCommandBase(const KUndo2MagicString &name, KisStrokesFacade *strokesFacade); ~KisSavedCommandBase() override; void undo() override; void redo() override; protected: virtual void addCommands(KisStrokeId id, bool undo) = 0; KisStrokesFacade* strokesFacade(); private: void runStroke(bool undo); private: KisStrokesFacade *m_strokesFacade; bool m_skipOneRedo; }; class KRITAIMAGE_EXPORT KisSavedCommand : public KisSavedCommandBase { public: KisSavedCommand(KUndo2CommandSP command, KisStrokesFacade *strokesFacade); int timedId() override; void setTimedID(int timedID) override; int id() const override; bool mergeWith(const KUndo2Command* command) override; bool timedMergeWith(KUndo2Command *other) override; QVector mergeCommandsVector() override; void setTime() override; QTime time() override; void setEndTime() override; QTime endTime() override; bool isMerged() override; protected: void addCommands(KisStrokeId id, bool undo) override; private: KUndo2CommandSP m_command; }; class KRITAIMAGE_EXPORT KisSavedMacroCommand : public KisSavedCommandBase { public: KisSavedMacroCommand(const KUndo2MagicString &name, KisStrokesFacade *strokesFacade); ~KisSavedMacroCommand() override; int id() const override; bool mergeWith(const KUndo2Command* command) override; void setMacroId(int value); void addCommand(KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality = KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity = KisStrokeJobData::NORMAL); - void performCancel(KisStrokeId id, bool strokeUndo); - void getCommandExecutionJobs(QVector *jobs, bool undo, bool shouldGoToHistory = true) const; void setOverrideInfo(const KisSavedMacroCommand *overriddenCommand, const QVector &skipWhileOverride); protected: void addCommands(KisStrokeId id, bool undo) override; private: struct Private; Private * const m_d; }; #endif /* __KIS_SAVED_COMMANDS_H */ diff --git a/libs/image/floodfill/kis_scanline_fill.cpp b/libs/image/floodfill/kis_scanline_fill.cpp index 26ce86a28f..60d43fc2b7 100644 --- a/libs/image/floodfill/kis_scanline_fill.cpp +++ b/libs/image/floodfill/kis_scanline_fill.cpp @@ -1,732 +1,732 @@ /* * 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_scanline_fill.h" #include #include #include #include #include #include "kis_image.h" #include "kis_fill_interval_map.h" #include "kis_pixel_selection.h" #include "kis_random_accessor_ng.h" #include "kis_fill_sanity_checks.h" template class CopyToSelection : public BaseClass { public: typedef KisRandomConstAccessorSP SourceAccessorType; SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) { return device->createRandomConstAccessorNG(0, 0); } public: void setDestinationSelection(KisPaintDeviceSP pixelSelection) { m_pixelSelection = pixelSelection; m_it = m_pixelSelection->createRandomAccessorNG(0,0); } ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) { Q_UNUSED(dstPtr); m_it->moveTo(x, y); *m_it->rawData() = opacity; } private: KisPaintDeviceSP m_pixelSelection; KisRandomAccessorSP m_it; }; template class FillWithColor : public BaseClass { public: typedef KisRandomAccessorSP SourceAccessorType; SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) { return device->createRandomAccessorNG(0, 0); } public: FillWithColor() : m_pixelSize(0) {} void setFillColor(const KoColor &sourceColor) { m_sourceColor = sourceColor; m_pixelSize = sourceColor.colorSpace()->pixelSize(); m_data = m_sourceColor.data(); } ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) { Q_UNUSED(x); Q_UNUSED(y); if (opacity == MAX_SELECTED) { memcpy(dstPtr, m_data, m_pixelSize); } } private: KoColor m_sourceColor; const quint8 *m_data; int m_pixelSize; }; template class FillWithColorExternal : public BaseClass { public: typedef KisRandomConstAccessorSP SourceAccessorType; SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) { return device->createRandomConstAccessorNG(0, 0); } public: void setDestinationDevice(KisPaintDeviceSP device) { m_externalDevice = device; m_it = m_externalDevice->createRandomAccessorNG(0,0); } void setFillColor(const KoColor &sourceColor) { m_sourceColor = sourceColor; m_pixelSize = sourceColor.colorSpace()->pixelSize(); m_data = m_sourceColor.data(); } ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) { Q_UNUSED(dstPtr); m_it->moveTo(x, y); if (opacity == MAX_SELECTED) { memcpy(m_it->rawData(), m_data, m_pixelSize); } } private: KisPaintDeviceSP m_externalDevice; KisRandomAccessorSP m_it; KoColor m_sourceColor; const quint8 *m_data {0}; - int m_pixelSize; + int m_pixelSize {0}; }; class DifferencePolicySlow { public: ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int threshold) { m_colorSpace = device->colorSpace(); m_srcPixel = srcPixel; m_srcPixelPtr = m_srcPixel.data(); m_threshold = threshold; } ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { if (m_threshold == 1) { if (memcmp(m_srcPixelPtr, pixelPtr, m_colorSpace->pixelSize()) == 0) { return 0; } return quint8_MAX; } else { return m_colorSpace->difference(m_srcPixelPtr, pixelPtr); } } private: const KoColorSpace *m_colorSpace; KoColor m_srcPixel; const quint8 *m_srcPixelPtr; int m_threshold; }; template class DifferencePolicyOptimized { typedef SrcPixelType HashKeyType; typedef QHash HashType; public: ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int threshold) { m_colorSpace = device->colorSpace(); m_srcPixel = srcPixel; m_srcPixelPtr = m_srcPixel.data(); m_threshold = threshold; } ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { HashKeyType key = *reinterpret_cast(pixelPtr); quint8 result; typename HashType::iterator it = m_differences.find(key); if (it != m_differences.end()) { result = *it; } else { if (m_threshold == 1) { if (memcmp(m_srcPixelPtr, pixelPtr, m_colorSpace->pixelSize()) == 0) { result = 0; } else { result = quint8_MAX; } } else { result = m_colorSpace->difference(m_srcPixelPtr, pixelPtr); } m_differences.insert(key, result); } return result; } private: HashType m_differences; const KoColorSpace *m_colorSpace; KoColor m_srcPixel; const quint8 *m_srcPixelPtr; int m_threshold; }; template class PixelFiller> class SelectionPolicy : public PixelFiller { public: typename PixelFiller::SourceAccessorType m_srcIt; public: SelectionPolicy(KisPaintDeviceSP device, const KoColor &srcPixel, int threshold) : m_threshold(threshold) { this->initDifferences(device, srcPixel, threshold); m_srcIt = this->createSourceDeviceAccessor(device); } ALWAYS_INLINE quint8 calculateOpacity(quint8* pixelPtr) { quint8 diff = this->calculateDifference(pixelPtr); if (!useSmoothSelection) { return diff <= m_threshold ? MAX_SELECTED : MIN_SELECTED; } else { quint8 selectionValue = qMax(0, m_threshold - diff); quint8 result = MIN_SELECTED; if (selectionValue > 0) { qreal selectionNorm = qreal(selectionValue) / m_threshold; result = MAX_SELECTED * selectionNorm; } return result; } } private: int m_threshold; }; class IsNonNullPolicySlow { public: - ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int /*threshold*/) { + ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int /*threshold*/) + { Q_UNUSED(srcPixel); - m_pixelSize = device->pixelSize(); m_testPixel.resize(m_pixelSize); } ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { if (memcmp(m_testPixel.data(), pixelPtr, m_pixelSize) == 0) { return 0; } return quint8_MAX; } private: - int m_pixelSize; + int m_pixelSize {0}; QByteArray m_testPixel; }; template class IsNonNullPolicyOptimized { public: ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int /*threshold*/) { Q_UNUSED(device); Q_UNUSED(srcPixel); } ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { SrcPixelType *pixel = reinterpret_cast(pixelPtr); return *pixel == 0; } }; class GroupSplitPolicy { public: typedef KisRandomAccessorSP SourceAccessorType; SourceAccessorType m_srcIt; public: GroupSplitPolicy(KisPaintDeviceSP scribbleDevice, KisPaintDeviceSP groupMapDevice, qint32 groupIndex, quint8 referenceValue, int threshold) : m_threshold(threshold), m_groupIndex(groupIndex), m_referenceValue(referenceValue) { KIS_SAFE_ASSERT_RECOVER_NOOP(m_groupIndex > 0); m_srcIt = scribbleDevice->createRandomAccessorNG(0,0); m_groupMapIt = groupMapDevice->createRandomAccessorNG(0,0); } ALWAYS_INLINE quint8 calculateOpacity(quint8* pixelPtr) { // TODO: either threshold should always be null, or there should be a special // case for *pixelPtr == 0, which is different from all the other groups, // whatever the threshold is int diff = qAbs(int(*pixelPtr) - m_referenceValue); return diff <= m_threshold ? MAX_SELECTED : MIN_SELECTED; } ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) { Q_UNUSED(opacity); // erase the scribble *dstPtr = 0; // write group index into the map m_groupMapIt->moveTo(x, y); qint32 *groupMapPtr = reinterpret_cast(m_groupMapIt->rawData()); if (*groupMapPtr != 0) { dbgImage << ppVar(*groupMapPtr) << ppVar(m_groupIndex); } KIS_SAFE_ASSERT_RECOVER_NOOP(*groupMapPtr == 0); *groupMapPtr = m_groupIndex; } private: int m_threshold; qint32 m_groupIndex; quint8 m_referenceValue; KisRandomAccessorSP m_groupMapIt; }; struct Q_DECL_HIDDEN KisScanlineFill::Private { KisPaintDeviceSP device; KisRandomAccessorSP it; QPoint startPoint; QRect boundingRect; int threshold; int rowIncrement; KisFillIntervalMap backwardMap; QStack forwardStack; inline void swapDirection() { rowIncrement *= -1; KIS_SAFE_ASSERT_RECOVER_NOOP(forwardStack.isEmpty() && "FATAL: the forward stack must be empty " "on a direction swap"); forwardStack = QStack(backwardMap.fetchAllIntervals(rowIncrement)); backwardMap.clear(); } }; KisScanlineFill::KisScanlineFill(KisPaintDeviceSP device, const QPoint &startPoint, const QRect &boundingRect) : m_d(new Private) { m_d->device = device; m_d->it = device->createRandomAccessorNG(startPoint.x(), startPoint.y()); m_d->startPoint = startPoint; m_d->boundingRect = boundingRect; m_d->rowIncrement = 1; m_d->threshold = 0; } KisScanlineFill::~KisScanlineFill() { } void KisScanlineFill::setThreshold(int threshold) { m_d->threshold = threshold; } template void KisScanlineFill::extendedPass(KisFillInterval *currentInterval, int srcRow, bool extendRight, T &pixelPolicy) { int x; int endX; int columnIncrement; int *intervalBorder; int *backwardIntervalBorder; KisFillInterval backwardInterval(currentInterval->start, currentInterval->end, srcRow); if (extendRight) { x = currentInterval->end; endX = m_d->boundingRect.right(); if (x >= endX) return; columnIncrement = 1; intervalBorder = ¤tInterval->end; backwardInterval.start = currentInterval->end + 1; backwardIntervalBorder = &backwardInterval.end; } else { x = currentInterval->start; endX = m_d->boundingRect.left(); if (x <= endX) return; columnIncrement = -1; intervalBorder = ¤tInterval->start; backwardInterval.end = currentInterval->start - 1; backwardIntervalBorder = &backwardInterval.start; } do { x += columnIncrement; pixelPolicy.m_srcIt->moveTo(x, srcRow); quint8 *pixelPtr = const_cast(pixelPolicy.m_srcIt->rawDataConst()); // TODO: avoid doing const_cast quint8 opacity = pixelPolicy.calculateOpacity(pixelPtr); if (opacity) { *intervalBorder = x; *backwardIntervalBorder = x; pixelPolicy.fillPixel(pixelPtr, opacity, x, srcRow); } else { break; } } while (x != endX); if (backwardInterval.isValid()) { m_d->backwardMap.insertInterval(backwardInterval); } } template void KisScanlineFill::processLine(KisFillInterval interval, const int rowIncrement, T &pixelPolicy) { m_d->backwardMap.cropInterval(&interval); if (!interval.isValid()) return; int firstX = interval.start; int lastX = interval.end; int x = firstX; int row = interval.row; int nextRow = row + rowIncrement; KisFillInterval currentForwardInterval; int numPixelsLeft = 0; quint8 *dataPtr = 0; const int pixelSize = m_d->device->pixelSize(); while(x <= lastX) { // a bit of optimzation for not calling slow random accessor // methods too often if (numPixelsLeft <= 0) { pixelPolicy.m_srcIt->moveTo(x, row); numPixelsLeft = pixelPolicy.m_srcIt->numContiguousColumns(x) - 1; dataPtr = const_cast(pixelPolicy.m_srcIt->rawDataConst()); } else { numPixelsLeft--; dataPtr += pixelSize; } quint8 *pixelPtr = dataPtr; quint8 opacity = pixelPolicy.calculateOpacity(pixelPtr); if (opacity) { if (!currentForwardInterval.isValid()) { currentForwardInterval.start = x; currentForwardInterval.end = x; currentForwardInterval.row = nextRow; } else { currentForwardInterval.end = x; } pixelPolicy.fillPixel(pixelPtr, opacity, x, row); if (x == firstX) { extendedPass(¤tForwardInterval, row, false, pixelPolicy); } if (x == lastX) { extendedPass(¤tForwardInterval, row, true, pixelPolicy); } } else { if (currentForwardInterval.isValid()) { m_d->forwardStack.push(currentForwardInterval); currentForwardInterval.invalidate(); } } x++; } if (currentForwardInterval.isValid()) { m_d->forwardStack.push(currentForwardInterval); } } template void KisScanlineFill::runImpl(T &pixelPolicy) { KIS_ASSERT_RECOVER_RETURN(m_d->forwardStack.isEmpty()); KisFillInterval startInterval(m_d->startPoint.x(), m_d->startPoint.x(), m_d->startPoint.y()); m_d->forwardStack.push(startInterval); /** * In the end of the first pass we should add an interval * containing the starting pixel, but directed into the opposite * direction. We cannot do it in the very beginning because the * intervals are offset by 1 pixel during every swap operation. */ bool firstPass = true; while (!m_d->forwardStack.isEmpty()) { while (!m_d->forwardStack.isEmpty()) { KisFillInterval interval = m_d->forwardStack.pop(); if (interval.row > m_d->boundingRect.bottom() || interval.row < m_d->boundingRect.top()) { continue; } processLine(interval, m_d->rowIncrement, pixelPolicy); } m_d->swapDirection(); if (firstPass) { startInterval.row--; m_d->forwardStack.push(startInterval); firstPass = false; } } } void KisScanlineFill::fillColor(const KoColor &originalFillColor) { KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y()); KoColor srcColor(it->rawDataConst(), m_d->device->colorSpace()); KoColor fillColor(originalFillColor); fillColor.convertTo(m_d->device->colorSpace()); const int pixelSize = m_d->device->pixelSize(); if (pixelSize == 1) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 2) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 4) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 8) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } else { SelectionPolicy policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } } void KisScanlineFill::fillColor(const KoColor &originalFillColor, KisPaintDeviceSP externalDevice) { KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y()); KoColor srcColor(it->rawDataConst(), m_d->device->colorSpace()); KoColor fillColor(originalFillColor); fillColor.convertTo(m_d->device->colorSpace()); const int pixelSize = m_d->device->pixelSize(); if (pixelSize == 1) { SelectionPolicy, FillWithColorExternal> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 2) { SelectionPolicy, FillWithColorExternal> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 4) { SelectionPolicy, FillWithColorExternal> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 8) { SelectionPolicy, FillWithColorExternal> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } else { SelectionPolicy policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } } void KisScanlineFill::fillSelection(KisPixelSelectionSP pixelSelection) { KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y()); KoColor srcColor(it->rawDataConst(), m_d->device->colorSpace()); const int pixelSize = m_d->device->pixelSize(); if (pixelSize == 1) { SelectionPolicy, CopyToSelection> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } else if (pixelSize == 2) { SelectionPolicy, CopyToSelection> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } else if (pixelSize == 4) { SelectionPolicy, CopyToSelection> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } else if (pixelSize == 8) { SelectionPolicy, CopyToSelection> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } else { SelectionPolicy policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } } void KisScanlineFill::clearNonZeroComponent() { const int pixelSize = m_d->device->pixelSize(); KoColor srcColor(Qt::transparent, m_d->device->colorSpace()); if (pixelSize == 1) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } else if (pixelSize == 2) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } else if (pixelSize == 4) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } else if (pixelSize == 8) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } else { SelectionPolicy policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } } void KisScanlineFill::fillContiguousGroup(KisPaintDeviceSP groupMapDevice, qint32 groupIndex) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->device->pixelSize() == 1); KIS_SAFE_ASSERT_RECOVER_RETURN(groupMapDevice->pixelSize() == 4); KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y()); const quint8 referenceValue = *it->rawDataConst(); GroupSplitPolicy policy(m_d->device, groupMapDevice, groupIndex, referenceValue, m_d->threshold); runImpl(policy); } void KisScanlineFill::testingProcessLine(const KisFillInterval &processInterval) { KoColor srcColor(QColor(0,0,0,0), m_d->device->colorSpace()); KoColor fillColor(QColor(200,200,200,200), m_d->device->colorSpace()); SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); processLine(processInterval, 1, policy); } QVector KisScanlineFill::testingGetForwardIntervals() const { return QVector(m_d->forwardStack); } KisFillIntervalMap* KisScanlineFill::testingGetBackwardIntervals() const { return &m_d->backwardMap; } diff --git a/libs/image/kis_async_merger.cpp b/libs/image/kis_async_merger.cpp index c0790e8986..b20080f160 100644 --- a/libs/image/kis_async_merger.cpp +++ b/libs/image/kis_async_merger.cpp @@ -1,380 +1,384 @@ /* Copyright (c) Dmitry Kazakov , 2009 * * 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_async_merger.h" #include #include #include #include #include "kis_node_visitor.h" #include "kis_painter.h" #include "kis_layer.h" #include "kis_group_layer.h" #include "kis_adjustment_layer.h" #include "generator/kis_generator_layer.h" #include "kis_external_layer_iface.h" #include "kis_paint_layer.h" #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_selection.h" #include "kis_clone_layer.h" #include "kis_processing_information.h" #include "kis_busy_progress_indicator.h" #include "kis_merge_walker.h" #include "kis_refresh_subtree_walker.h" #include "kis_abstract_projection_plane.h" //#define DEBUG_MERGER #ifdef DEBUG_MERGER #define DEBUG_NODE_ACTION(message, type, leaf, rect) \ qDebug() << message << type << ":" << leaf->node()->name() << rect #else #define DEBUG_NODE_ACTION(message, type, leaf, rect) #endif class KisUpdateOriginalVisitor : public KisNodeVisitor { public: KisUpdateOriginalVisitor(const QRect &updateRect, KisPaintDeviceSP projection, const QRect &cropRect) : m_updateRect(updateRect), m_cropRect(cropRect), m_projection(projection) { } ~KisUpdateOriginalVisitor() override { } public: using KisNodeVisitor::visit; bool visit(KisAdjustmentLayer* layer) override { if (!layer->visible()) return true; if (!m_projection) { warnImage << "ObligeChild mechanism has been activated for " "an adjustment layer! Do nothing..."; layer->original()->clear(); return true; } const QRect originalUpdateRect = layer->projectionPlane()->needRectForOriginal(m_updateRect); KisPaintDeviceSP originalDevice = layer->original(); originalDevice->clear(originalUpdateRect); const QRect applyRect = originalUpdateRect & m_projection->extent(); // If the intersection of the updaterect and the projection extent is // null, we are finish here. if(applyRect.isNull()) return true; KisFilterConfigurationSP filterConfig = layer->filter(); if (!filterConfig) { /** * When an adjustment layer is just created, it may have no * filter inside. Then the layer has work as a pass-through * node. Just copy the merged data to the layer's original. */ KisPainter::copyAreaOptimized(applyRect.topLeft(), m_projection, originalDevice, applyRect); return true; } KisSelectionSP selection = layer->fetchComposedInternalSelection(applyRect); const QRect filterRect = selection ? applyRect & selection->selectedRect() : applyRect; KisFilterSP filter = KisFilterRegistry::instance()->value(filterConfig->name()); if (!filter) return false; KisPaintDeviceSP dstDevice = originalDevice; if (selection) { dstDevice = new KisPaintDevice(originalDevice->colorSpace()); } if (!filterRect.isEmpty()) { KIS_ASSERT_RECOVER_NOOP(layer->busyProgressIndicator()); layer->busyProgressIndicator()->update(); // We do not create a transaction here, as srcDevice != dstDevice filter->process(m_projection, dstDevice, 0, filterRect, filterConfig.data(), 0); } if (selection) { KisPainter::copyAreaOptimized(applyRect.topLeft(), m_projection, originalDevice, applyRect); KisPainter::copyAreaOptimized(filterRect.topLeft(), dstDevice, originalDevice, filterRect, selection); } return true; } bool visit(KisExternalLayer*) override { return true; } bool visit(KisGeneratorLayer*) override { return true; } bool visit(KisPaintLayer*) override { return true; } bool visit(KisGroupLayer*) override { return true; } bool visit(KisCloneLayer *layer) override { QRect emptyRect; KisRefreshSubtreeWalker walker(emptyRect); KisAsyncMerger merger; KisLayerSP srcLayer = layer->copyFrom(); QRect srcRect = m_updateRect.translated(-layer->x(), -layer->y()); QRegion prepareRegion(srcRect); prepareRegion -= m_cropRect; /** * If a clone has complicated masks, we should prepare additional * source area to ensure the rect is prepared. */ QRect needRectOnSource = layer->needRectOnSourceForMasks(srcRect); if (!needRectOnSource.isEmpty()) { prepareRegion += needRectOnSource; } + if (srcLayer.isNull()) { + return true; + } + Q_FOREACH (const QRect &rect, prepareRegion.rects()) { walker.collectRects(srcLayer, rect); merger.startMerge(walker, false); } return true; } bool visit(KisNode*) override { return true; } bool visit(KisFilterMask*) override { return true; } bool visit(KisTransformMask*) override { return true; } bool visit(KisTransparencyMask*) override { return true; } bool visit(KisSelectionMask*) override { return true; } bool visit(KisColorizeMask*) override { return true; } private: QRect m_updateRect; QRect m_cropRect; KisPaintDeviceSP m_projection; }; /*********************************************************************/ /* KisAsyncMerger */ /*********************************************************************/ void KisAsyncMerger::startMerge(KisBaseRectsWalker &walker, bool notifyClones) { KisMergeWalker::LeafStack &leafStack = walker.leafStack(); const bool useTempProjections = walker.needRectVaries(); while(!leafStack.isEmpty()) { KisMergeWalker::JobItem item = leafStack.pop(); KisProjectionLeafSP currentLeaf = item.m_leaf; /** * In some unidentified cases teh nodes might be removed * while the updates are still running. We have no proof * of it yet, so just add a safety assert here. */ KIS_SAFE_ASSERT_RECOVER_RETURN(currentLeaf); KIS_SAFE_ASSERT_RECOVER_RETURN(currentLeaf->node()); // All the masks should be filtered by the walkers KIS_SAFE_ASSERT_RECOVER_RETURN(currentLeaf->isLayer()); QRect applyRect = item.m_applyRect; if (currentLeaf->isRoot()) { currentLeaf->projectionPlane()->recalculate(applyRect, walker.startNode()); continue; } if(item.m_position & KisMergeWalker::N_EXTRA) { // The type of layers that will not go to projection. DEBUG_NODE_ACTION("Updating", "N_EXTRA", currentLeaf, applyRect); KisUpdateOriginalVisitor originalVisitor(applyRect, m_currentProjection, walker.cropRect()); currentLeaf->accept(originalVisitor); currentLeaf->projectionPlane()->recalculate(applyRect, currentLeaf->node()); continue; } if (!m_currentProjection) { setupProjection(currentLeaf, applyRect, useTempProjections); } KisUpdateOriginalVisitor originalVisitor(applyRect, m_currentProjection, walker.cropRect()); if(item.m_position & KisMergeWalker::N_FILTHY) { DEBUG_NODE_ACTION("Updating", "N_FILTHY", currentLeaf, applyRect); if (currentLeaf->visible() || currentLeaf->hasClones()) { currentLeaf->accept(originalVisitor); currentLeaf->projectionPlane()->recalculate(applyRect, walker.startNode()); } } else if(item.m_position & KisMergeWalker::N_ABOVE_FILTHY) { DEBUG_NODE_ACTION("Updating", "N_ABOVE_FILTHY", currentLeaf, applyRect); if(currentLeaf->dependsOnLowerNodes()) { if (currentLeaf->visible() || currentLeaf->hasClones()) { currentLeaf->accept(originalVisitor); currentLeaf->projectionPlane()->recalculate(applyRect, currentLeaf->node()); } } } else if(item.m_position & KisMergeWalker::N_FILTHY_PROJECTION) { DEBUG_NODE_ACTION("Updating", "N_FILTHY_PROJECTION", currentLeaf, applyRect); if (currentLeaf->visible() || currentLeaf->hasClones()) { currentLeaf->projectionPlane()->recalculate(applyRect, walker.startNode()); } } else /*if(item.m_position & KisMergeWalker::N_BELOW_FILTHY)*/ { DEBUG_NODE_ACTION("Updating", "N_BELOW_FILTHY", currentLeaf, applyRect); /* nothing to do */ } compositeWithProjection(currentLeaf, applyRect); if(item.m_position & KisMergeWalker::N_TOPMOST) { writeProjection(currentLeaf, useTempProjections, applyRect); resetProjection(); } // FIXME: remove it from the inner loop and/or change to a warning! Q_ASSERT(currentLeaf->projection()->defaultBounds()->currentLevelOfDetail() == walker.levelOfDetail()); } if(notifyClones) { doNotifyClones(walker); } if(m_currentProjection) { warnImage << "BUG: The walker hasn't reached the root layer!"; warnImage << " Start node:" << walker.startNode() << "Requested rect:" << walker.requestedRect(); warnImage << " An inconsistency in the walkers occurred!"; warnImage << " Please report a bug describing how you got this message."; // reset projection to avoid artifacts in next merges and allow people to work further resetProjection(); } } void KisAsyncMerger::resetProjection() { m_currentProjection = 0; m_finalProjection = 0; } void KisAsyncMerger::setupProjection(KisProjectionLeafSP currentLeaf, const QRect& rect, bool useTempProjection) { KisPaintDeviceSP parentOriginal = currentLeaf->parent()->original(); if (parentOriginal != currentLeaf->projection()) { if (useTempProjection) { if(!m_cachedPaintDevice) m_cachedPaintDevice = new KisPaintDevice(parentOriginal->colorSpace()); m_currentProjection = m_cachedPaintDevice; m_currentProjection->prepareClone(parentOriginal); m_finalProjection = parentOriginal; } else { parentOriginal->clear(rect); m_finalProjection = m_currentProjection = parentOriginal; } } else { /** * It happened so that our parent uses our own projection as * its original. It means obligeChild mechanism works. * We won't initialise m_currentProjection. This will cause * writeProjection() and compositeWithProjection() do nothing * when called. */ /* NOP */ } } void KisAsyncMerger::writeProjection(KisProjectionLeafSP topmostLeaf, bool useTempProjection, const QRect &rect) { Q_UNUSED(useTempProjection); Q_UNUSED(topmostLeaf); if (!m_currentProjection) return; if(m_currentProjection != m_finalProjection) { KisPainter::copyAreaOptimized(rect.topLeft(), m_currentProjection, m_finalProjection, rect); } DEBUG_NODE_ACTION("Writing projection", "", topmostLeaf->parent(), rect); } bool KisAsyncMerger::compositeWithProjection(KisProjectionLeafSP leaf, const QRect &rect) { if (!m_currentProjection) return true; if (!leaf->visible()) return true; KisPainter gc(m_currentProjection); leaf->projectionPlane()->apply(&gc, rect); DEBUG_NODE_ACTION("Compositing projection", "", leaf, rect); return true; } void KisAsyncMerger::doNotifyClones(KisBaseRectsWalker &walker) { KisBaseRectsWalker::CloneNotificationsVector &vector = walker.cloneNotifications(); KisBaseRectsWalker::CloneNotificationsVector::iterator it; for(it = vector.begin(); it != vector.end(); ++it) { (*it).notify(); } } diff --git a/libs/image/kis_brush_mask_applicator_factories.cpp b/libs/image/kis_brush_mask_applicator_factories.cpp index 860440aead..8fefa3fde1 100644 --- a/libs/image/kis_brush_mask_applicator_factories.cpp +++ b/libs/image/kis_brush_mask_applicator_factories.cpp @@ -1,648 +1,645 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_brush_mask_applicator_factories.h" #include "vc_extra_math.h" #include "kis_circle_mask_generator.h" #include "kis_circle_mask_generator_p.h" #include "kis_gauss_circle_mask_generator_p.h" #include "kis_curve_circle_mask_generator_p.h" #include "kis_gauss_rect_mask_generator_p.h" #include "kis_curve_rect_mask_generator_p.h" #include "kis_rect_mask_generator_p.h" #include "kis_brush_mask_applicators.h" #include "kis_brush_mask_applicator_base.h" #define a(_s) #_s #define b(_s) a(_s) template<> template<> MaskApplicatorFactory::ReturnType MaskApplicatorFactory::create(ParamType maskGenerator) { return new KisBrushMaskScalarApplicator(maskGenerator); } template<> template<> MaskApplicatorFactory::ReturnType MaskApplicatorFactory::create(ParamType maskGenerator) { return new KisBrushMaskVectorApplicator(maskGenerator); } template<> template<> MaskApplicatorFactory::ReturnType MaskApplicatorFactory::create(ParamType maskGenerator) { return new KisBrushMaskVectorApplicator(maskGenerator); } template<> template<> MaskApplicatorFactory::ReturnType MaskApplicatorFactory::create(ParamType maskGenerator) { return new KisBrushMaskVectorApplicator(maskGenerator); } template<> template<> MaskApplicatorFactory::ReturnType MaskApplicatorFactory::create(ParamType maskGenerator) { return new KisBrushMaskVectorApplicator(maskGenerator); } template<> template<> MaskApplicatorFactory::ReturnType MaskApplicatorFactory::create(ParamType maskGenerator) { return new KisBrushMaskVectorApplicator(maskGenerator); } template<> template<> MaskApplicatorFactory::ReturnType MaskApplicatorFactory::create(ParamType maskGenerator) { return new KisBrushMaskVectorApplicator(maskGenerator); } #if defined HAVE_VC struct KisCircleMaskGenerator::FastRowProcessor { FastRowProcessor(KisCircleMaskGenerator *maskGenerator) : d(maskGenerator->d.data()) {} template void process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY); KisCircleMaskGenerator::Private *d; }; template<> void KisCircleMaskGenerator:: FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY) { const bool useSmoothing = d->copyOfAntialiasEdges; float y_ = y - centerY; float sinay_ = sina * y_; float cosay_ = cosa * y_; float* bufferPointer = buffer; Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); Vc::float_v increment((float)Vc::float_v::size()); Vc::float_v vCenterX(centerX); Vc::float_v vCosa(cosa); Vc::float_v vSina(sina); Vc::float_v vCosaY_(cosay_); Vc::float_v vSinaY_(sinay_); Vc::float_v vXCoeff(d->xcoef); Vc::float_v vYCoeff(d->ycoef); Vc::float_v vTransformedFadeX(d->transformedFadeX); Vc::float_v vTransformedFadeY(d->transformedFadeY); Vc::float_v vOne(Vc::One); for (int i=0; i < width; i+= Vc::float_v::size()){ Vc::float_v x_ = currentIndices - vCenterX; Vc::float_v xr = x_ * vCosa - vSinaY_; Vc::float_v yr = x_ * vSina + vCosaY_; Vc::float_v n = pow2(xr * vXCoeff) + pow2(yr * vYCoeff); Vc::float_m outsideMask = n > vOne; if (!outsideMask.isFull()) { if (useSmoothing) { xr = Vc::abs(xr) + vOne; yr = Vc::abs(yr) + vOne; } Vc::float_v vNormFade = pow2(xr * vTransformedFadeX) + pow2(yr * vTransformedFadeY); Vc::float_m vNormLowMask = vNormFade < vOne; vNormFade.setZero(vNormLowMask); //255 * n * (normeFade - 1) / (normeFade - n) Vc::float_v vFade = n * (vNormFade - vOne) / (vNormFade - n); // Mask in the inner circle of the mask Vc::float_m mask = vNormFade < vOne; vFade.setZero(mask); // Mask out the outer circle of the mask vFade(outsideMask) = vOne; vFade.store(bufferPointer, Vc::Aligned); } else { // Mask out everything outside the circle vOne.store(bufferPointer, Vc::Aligned); } currentIndices = currentIndices + increment; bufferPointer += Vc::float_v::size(); } } struct KisGaussCircleMaskGenerator::FastRowProcessor { FastRowProcessor(KisGaussCircleMaskGenerator *maskGenerator) : d(maskGenerator->d.data()) {} template void process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY); KisGaussCircleMaskGenerator::Private *d; }; template<> void KisGaussCircleMaskGenerator:: FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY) { float y_ = y - centerY; float sinay_ = sina * y_; float cosay_ = cosa * y_; float* bufferPointer = buffer; Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); Vc::float_v increment((float)Vc::float_v::size()); Vc::float_v vCenterX(centerX); Vc::float_v vCenter(d->center); Vc::float_v vCosa(cosa); Vc::float_v vSina(sina); Vc::float_v vCosaY_(cosay_); Vc::float_v vSinaY_(sinay_); Vc::float_v vYCoeff(d->ycoef); Vc::float_v vDistfactor(d->distfactor); Vc::float_v vAlphafactor(d->alphafactor); Vc::float_v vZero(Vc::Zero); Vc::float_v vValMax(255.f); for (int i=0; i < width; i+= Vc::float_v::size()){ Vc::float_v x_ = currentIndices - vCenterX; Vc::float_v xr = x_ * vCosa - vSinaY_; Vc::float_v yr = x_ * vSina + vCosaY_; Vc::float_v dist = sqrt(pow2(xr) + pow2(yr * vYCoeff)); // Apply FadeMaker mask and operations Vc::float_m excludeMask = d->fadeMaker.needFade(dist); if (!excludeMask.isFull()) { Vc::float_v valDist = dist * vDistfactor; Vc::float_v fullFade = vAlphafactor * ( VcExtraMath::erf(valDist + vCenter) - VcExtraMath::erf(valDist - vCenter)); Vc::float_m mask; // Mask in the inner circle of the mask mask = fullFade < vZero; fullFade.setZero(mask); // Mask the outer circle mask = fullFade > 254.974f; fullFade(mask) = vValMax; // Mask (value - value), precision errors. Vc::float_v vFade = (vValMax - fullFade) / vValMax; // return original dist values before vFade transform vFade(excludeMask) = dist; vFade.store(bufferPointer, Vc::Aligned); } else { dist.store(bufferPointer, Vc::Aligned); } currentIndices = currentIndices + increment; bufferPointer += Vc::float_v::size(); } } struct KisCurveCircleMaskGenerator::FastRowProcessor { FastRowProcessor(KisCurveCircleMaskGenerator *maskGenerator) : d(maskGenerator->d.data()) {} template void process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY); KisCurveCircleMaskGenerator::Private *d; }; template<> void KisCurveCircleMaskGenerator:: FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY) { float y_ = y - centerY; float sinay_ = sina * y_; float cosay_ = cosa * y_; float* bufferPointer = buffer; qreal* curveDataPointer = d->curveData.data(); Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); Vc::float_v increment((float)Vc::float_v::size()); Vc::float_v vCenterX(centerX); Vc::float_v vCosa(cosa); Vc::float_v vSina(sina); Vc::float_v vCosaY_(cosay_); Vc::float_v vSinaY_(sinay_); Vc::float_v vYCoeff(d->ycoef); Vc::float_v vXCoeff(d->xcoef); Vc::float_v vCurveResolution(d->curveResolution); Vc::float_v vCurvedData(Vc::Zero); Vc::float_v vCurvedData1(Vc::Zero); Vc::float_v vOne(Vc::One); Vc::float_v vZero(Vc::Zero); for (int i=0; i < width; i+= Vc::float_v::size()){ Vc::float_v x_ = currentIndices - vCenterX; Vc::float_v xr = x_ * vCosa - vSinaY_; Vc::float_v yr = x_ * vSina + vCosaY_; Vc::float_v dist = pow2(xr * vXCoeff) + pow2(yr * vYCoeff); // Apply FadeMaker mask and operations Vc::float_m excludeMask = d->fadeMaker.needFade(dist); if (!excludeMask.isFull()) { Vc::float_v valDist = dist * vCurveResolution; // truncate Vc::float_v::IndexType vAlphaValue(valDist); Vc::float_v vFloatAlphaValue = vAlphaValue; Vc::float_v alphaValueF = valDist - vFloatAlphaValue; vCurvedData.gather(curveDataPointer,vAlphaValue); vCurvedData1.gather(curveDataPointer,vAlphaValue + 1); // Vc::float_v vCurvedData1(curveDataPointer,vAlphaValue + 1); // vAlpha Vc::float_v fullFade = ( (vOne - alphaValueF) * vCurvedData + alphaValueF * vCurvedData1); Vc::float_m mask; // Mask in the inner circle of the mask mask = fullFade < vZero; fullFade.setZero(mask); // Mask outer circle of mask mask = fullFade >= vOne; Vc::float_v vFade = (vOne - fullFade); vFade.setZero(mask); // return original dist values before vFade transform vFade(excludeMask) = dist; vFade.store(bufferPointer, Vc::Aligned); } else { dist.store(bufferPointer, Vc::Aligned); } currentIndices = currentIndices + increment; bufferPointer += Vc::float_v::size(); } } struct KisGaussRectangleMaskGenerator::FastRowProcessor { FastRowProcessor(KisGaussRectangleMaskGenerator *maskGenerator) : d(maskGenerator->d.data()) {} template void process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY); KisGaussRectangleMaskGenerator::Private *d; }; struct KisRectangleMaskGenerator::FastRowProcessor { FastRowProcessor(KisRectangleMaskGenerator *maskGenerator) : d(maskGenerator->d.data()) {} template void process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY); KisRectangleMaskGenerator::Private *d; }; template<> void KisRectangleMaskGenerator:: FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY) { const bool useSmoothing = d->copyOfAntialiasEdges; float y_ = y - centerY; float sinay_ = sina * y_; float cosay_ = cosa * y_; float* bufferPointer = buffer; Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); Vc::float_v increment((float)Vc::float_v::size()); Vc::float_v vCenterX(centerX); Vc::float_v vCosa(cosa); Vc::float_v vSina(sina); Vc::float_v vCosaY_(cosay_); Vc::float_v vSinaY_(sinay_); Vc::float_v vXCoeff(d->xcoeff); Vc::float_v vYCoeff(d->ycoeff); Vc::float_v vTransformedFadeX(d->transformedFadeX); Vc::float_v vTransformedFadeY(d->transformedFadeY); Vc::float_v vOne(Vc::One); Vc::float_v vZero(Vc::Zero); Vc::float_v vTolerance(10000.f); for (int i=0; i < width; i+= Vc::float_v::size()){ Vc::float_v x_ = currentIndices - vCenterX; Vc::float_v xr = Vc::abs(x_ * vCosa - vSinaY_); Vc::float_v yr = Vc::abs(x_ * vSina + vCosaY_); Vc::float_v nxr = xr * vXCoeff; Vc::float_v nyr = yr * vYCoeff; Vc::float_m outsideMask = (nxr > vOne) || (nyr > vOne); if (!outsideMask.isFull()) { if (useSmoothing) { xr = Vc::abs(xr) + vOne; yr = Vc::abs(yr) + vOne; } Vc::float_v fxr = xr * vTransformedFadeX; Vc::float_v fyr = yr * vTransformedFadeY; Vc::float_v fxrNorm = nxr * (fxr - vOne) / (fxr - nxr); Vc::float_v fyrNorm = nyr * (fyr - vOne) / (fyr - nyr); Vc::float_v vFade(vZero); - Vc::float_v::IndexType fxrInt(fxr * vTolerance); - Vc::float_v::IndexType fyrInt(fyr * vTolerance); - - Vc::float_m fadeXMask = (fxr > vOne) && ((fxrInt >= fyrInt) || fyr < vOne); - Vc::float_m fadeYMask = (fyr > vOne) && ((fyrInt > fxrInt) || fxr < vOne); - - vFade(fadeXMask) = fxrNorm; - vFade(!fadeXMask && fadeYMask) = fyrNorm; + Vc::float_m vFadeMask = fxrNorm < fyrNorm; + Vc::float_v vMaxVal = vFade; + vMaxVal(fxr > vOne) = fxrNorm; + vMaxVal(vFadeMask && fyr > vOne) = fyrNorm; + vFade = vMaxVal; // Mask out the outer circle of the mask vFade(outsideMask) = vOne; vFade.store(bufferPointer, Vc::Aligned); } else { // Mask out everything outside the circle vOne.store(bufferPointer, Vc::Aligned); } currentIndices = currentIndices + increment; bufferPointer += Vc::float_v::size(); } } template<> void KisGaussRectangleMaskGenerator:: FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY) { float y_ = y - centerY; float sinay_ = sina * y_; float cosay_ = cosa * y_; float* bufferPointer = buffer; Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); Vc::float_v increment((float)Vc::float_v::size()); Vc::float_v vCenterX(centerX); Vc::float_v vCosa(cosa); Vc::float_v vSina(sina); Vc::float_v vCosaY_(cosay_); Vc::float_v vSinaY_(sinay_); Vc::float_v vhalfWidth(d->halfWidth); Vc::float_v vhalfHeight(d->halfHeight); Vc::float_v vXFade(d->xfade); Vc::float_v vYFade(d->yfade); Vc::float_v vAlphafactor(d->alphafactor); Vc::float_v vOne(Vc::One); Vc::float_v vZero(Vc::Zero); Vc::float_v vValMax(255.f); for (int i=0; i < width; i+= Vc::float_v::size()){ Vc::float_v x_ = currentIndices - vCenterX; Vc::float_v xr = x_ * vCosa - vSinaY_; Vc::float_v yr = Vc::abs(x_ * vSina + vCosaY_); Vc::float_v vValue; // check if we need to apply fader on values Vc::float_m excludeMask = d->fadeMaker.needFade(xr,yr); vValue(excludeMask) = vOne; if (!excludeMask.isFull()) { Vc::float_v fullFade = vValMax - (vAlphafactor * (VcExtraMath::erf((vhalfWidth + xr) * vXFade) + VcExtraMath::erf((vhalfWidth - xr) * vXFade)) * (VcExtraMath::erf((vhalfHeight + yr) * vYFade) + VcExtraMath::erf((vhalfHeight - yr) * vYFade))); // apply antialias fader d->fadeMaker.apply2DFader(fullFade,excludeMask,xr,yr); Vc::float_m mask; // Mask in the inner circle of the mask mask = fullFade < vZero; fullFade.setZero(mask); // Mask the outer circle mask = fullFade > 254.974f; fullFade(mask) = vValMax; // Mask (value - value), precision errors. Vc::float_v vFade = fullFade / vValMax; // return original vValue values before vFade transform vFade(excludeMask) = vValue; vFade.store(bufferPointer, Vc::Aligned); } else { vValue.store(bufferPointer, Vc::Aligned); } currentIndices = currentIndices + increment; bufferPointer += Vc::float_v::size(); } } struct KisCurveRectangleMaskGenerator::FastRowProcessor { FastRowProcessor(KisCurveRectangleMaskGenerator *maskGenerator) : d(maskGenerator->d.data()) {} template void process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY); KisCurveRectangleMaskGenerator::Private *d; }; template<> void KisCurveRectangleMaskGenerator:: FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY) { float y_ = y - centerY; float sinay_ = sina * y_; float cosay_ = cosa * y_; float* bufferPointer = buffer; qreal* curveDataPointer = d->curveData.data(); Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); Vc::float_v increment((float)Vc::float_v::size()); Vc::float_v vCenterX(centerX); Vc::float_v vCosa(cosa); Vc::float_v vSina(sina); Vc::float_v vCosaY_(cosay_); Vc::float_v vSinaY_(sinay_); Vc::float_v vYCoeff(d->ycoeff); Vc::float_v vXCoeff(d->xcoeff); Vc::float_v vCurveResolution(d->curveResolution); Vc::float_v vOne(Vc::One); Vc::float_v vZero(Vc::Zero); Vc::float_v vValMax(255.f); for (int i=0; i < width; i+= Vc::float_v::size()){ Vc::float_v x_ = currentIndices - vCenterX; Vc::float_v xr = x_ * vCosa - vSinaY_; Vc::float_v yr = Vc::abs(x_ * vSina + vCosaY_); Vc::float_v vValue; // check if we need to apply fader on values Vc::float_m excludeMask = d->fadeMaker.needFade(xr,yr); vValue(excludeMask) = vOne; if (!excludeMask.isFull()) { // We need to mask the extra area given for aliniation // the next operation should never give values above 1 Vc::float_v preSIndex = Vc::abs(xr) * vXCoeff; Vc::float_v preTIndex = Vc::abs(yr) * vYCoeff; preSIndex(preSIndex > vOne) = vOne; preTIndex(preTIndex > vOne) = vOne; Vc::float_v::IndexType sIndex( round(preSIndex * vCurveResolution)); Vc::float_v::IndexType tIndex( round(preTIndex * vCurveResolution)); Vc::float_v::IndexType sIndexInverted = vCurveResolution - sIndex; Vc::float_v::IndexType tIndexInverted = vCurveResolution - tIndex; Vc::float_v vCurvedDataSIndex(curveDataPointer, sIndex); Vc::float_v vCurvedDataTIndex(curveDataPointer, tIndex); Vc::float_v vCurvedDataSIndexInv(curveDataPointer, sIndexInverted); Vc::float_v vCurvedDataTIndexInv(curveDataPointer, tIndexInverted); Vc::float_v fullFade = vValMax * (vOne - (vCurvedDataSIndex * (vOne - vCurvedDataSIndexInv) * vCurvedDataTIndex * (vOne - vCurvedDataTIndexInv))); // apply antialias fader d->fadeMaker.apply2DFader(fullFade,excludeMask,xr,yr); Vc::float_m mask; // Mask in the inner circle of the mask mask = fullFade < vZero; fullFade.setZero(mask); // Mask the outer circle mask = fullFade > 254.974f; fullFade(mask) = vValMax; // Mask (value - value), precision errors. Vc::float_v vFade = fullFade / vValMax; // return original vValue values before vFade transform vFade(excludeMask) = vValue; vFade.store(bufferPointer, Vc::Aligned); } else { vValue.store(bufferPointer, Vc::Aligned); } currentIndices = currentIndices + increment; bufferPointer += Vc::float_v::size(); } } #endif /* defined HAVE_VC */ diff --git a/libs/image/kis_convolution_worker_spatial.h b/libs/image/kis_convolution_worker_spatial.h index cf3a101ee5..c5516deb72 100644 --- a/libs/image/kis_convolution_worker_spatial.h +++ b/libs/image/kis_convolution_worker_spatial.h @@ -1,386 +1,386 @@ /* * Copyright (c) 2005, 2008, 2010 Cyrille Berger * Copyright (c) 2009, 2010 Edward Apap * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_CONVOLUTION_WORKER_SPATIAL_H #define KIS_CONVOLUTION_WORKER_SPATIAL_H #include "kis_convolution_worker.h" #include "kis_math_toolbox.h" template class KisConvolutionWorkerSpatial : public KisConvolutionWorker<_IteratorFactory_> { public: KisConvolutionWorkerSpatial(KisPainter *painter, KoUpdater *progress) : KisConvolutionWorker<_IteratorFactory_>(painter, progress) , m_alphaCachePos(-1) , m_alphaRealPos(-1) , m_pixelPtrCache(0) , m_pixelPtrCacheCopy(0) , m_minClamp(0) , m_maxClamp(0) , m_absoluteOffset(0) { } ~KisConvolutionWorkerSpatial() override { } inline void loadPixelToCache(qreal **cache, const quint8 *data, int index) { // no alpha is rare case, so just multiply by 1.0 in that case qreal alphaValue = m_alphaRealPos >= 0 ? m_toDoubleFuncPtr[m_alphaCachePos](data, m_alphaRealPos) : 1.0; for (quint32 k = 0; k < m_convolveChannelsNo; ++k) { if (k != (quint32)m_alphaCachePos) { const quint32 channelPos = m_convChannelList[k]->pos(); cache[index][k] = m_toDoubleFuncPtr[k](data, channelPos) * alphaValue; } else { cache[index][k] = alphaValue; } } } void execute(const KisConvolutionKernelSP kernel, const KisPaintDeviceSP src, QPoint srcPos, QPoint dstPos, QSize areaSize, const QRect& dataRect) override { // store some kernel characteristics m_kw = kernel->width(); m_kh = kernel->height(); - m_khalfWidth = (m_kw - 1) / 2; - m_khalfHeight = (m_kh - 1) / 2; + m_khalfWidth = (m_kw > 0) ? (m_kw - 1) / 2 : m_kw; + m_khalfHeight = (m_kh > 0) ? (m_kh - 1) / 2 : m_kh; m_cacheSize = m_kw * m_kh; m_pixelSize = src->colorSpace()->pixelSize(); quint32 channelCount = src->colorSpace()->channelCount(); m_kernelData = new qreal[m_cacheSize]; qreal *kernelDataPtr = m_kernelData; // fill in data for (quint32 r = 0; r < kernel->height(); r++) { for (quint32 c = 0; c < kernel->width(); c++) { *kernelDataPtr = (*(kernel->data()))(r, c); kernelDataPtr++; } } // Make the area we cover as small as possible if (this->m_painter->selection()) { QRect r = this->m_painter->selection()->selectedRect().intersected(QRect(srcPos, areaSize)); dstPos += r.topLeft() - srcPos; srcPos = r.topLeft(); areaSize = r.size(); } if (areaSize.width() == 0 || areaSize.height() == 0) return; // Don't convolve with an even sized kernel Q_ASSERT((m_kw & 0x01) == 1 || (m_kh & 0x01) == 1 || kernel->factor() != 0); // find out which channels need be convolved m_convChannelList = this->convolvableChannelList(src); m_convolveChannelsNo = m_convChannelList.count(); for (int i = 0; i < m_convChannelList.size(); i++) { if (m_convChannelList[i]->channelType() == KoChannelInfo::ALPHA) { m_alphaCachePos = i; m_alphaRealPos = m_convChannelList[i]->pos(); } } bool hasProgressUpdater = this->m_progress; if (hasProgressUpdater) this->m_progress->setProgress(0); // Iterate over all pixels in our rect, create a cache of pixels around the current pixel and convolve them. m_pixelPtrCache = new qreal*[m_cacheSize]; m_pixelPtrCacheCopy = new qreal*[m_cacheSize]; for (quint32 c = 0; c < m_cacheSize; ++c) { m_pixelPtrCache[c] = new qreal[channelCount]; m_pixelPtrCacheCopy[c] = new qreal[channelCount]; } // decide caching strategy enum TraversingDirection { Horizontal, Vertical }; TraversingDirection traversingDirection = Vertical; if (m_kw > m_kh) { traversingDirection = Horizontal; } KisMathToolbox mathToolbox; m_toDoubleFuncPtr = QVector(m_convolveChannelsNo); if (!mathToolbox.getToDoubleChannelPtr(m_convChannelList, m_toDoubleFuncPtr)) return; m_fromDoubleFuncPtr = QVector(m_convolveChannelsNo); if (!mathToolbox.getFromDoubleChannelPtr(m_convChannelList, m_fromDoubleFuncPtr)) return; m_kernelFactor = kernel->factor() ? 1.0 / kernel->factor() : 1; m_maxClamp = new qreal[m_convChannelList.count()]; m_minClamp = new qreal[m_convChannelList.count()]; m_absoluteOffset = new qreal[m_convChannelList.count()]; for (quint16 i = 0; i < m_convChannelList.count(); ++i) { m_minClamp[i] = mathToolbox.minChannelValue(m_convChannelList[i]); m_maxClamp[i] = mathToolbox.maxChannelValue(m_convChannelList[i]); m_absoluteOffset[i] = (m_maxClamp[i] - m_minClamp[i]) * kernel->offset(); } qint32 row = srcPos.y(); qint32 col = srcPos.x(); // populate pixelPtrCacheCopy for starting position (0, 0) qint32 i = 0; typename _IteratorFactory_::HLineConstIterator hitInitSrc = _IteratorFactory_::createHLineConstIterator(src, col - m_khalfWidth, row - m_khalfHeight, m_kw, dataRect); for (quint32 krow = 0; krow < m_kh; ++krow) { do { const quint8* data = hitInitSrc->oldRawData(); loadPixelToCache(m_pixelPtrCacheCopy, data, i); ++i; } while (hitInitSrc->nextPixel()); hitInitSrc->nextRow(); } if (traversingDirection == Horizontal) { if(hasProgressUpdater) { this->m_progress->setRange(0, areaSize.height()); } typename _IteratorFactory_::HLineIterator hitDst = _IteratorFactory_::createHLineIterator(this->m_painter->device(), dstPos.x(), dstPos.y(), areaSize.width(), dataRect); typename _IteratorFactory_::HLineConstIterator hitSrc = _IteratorFactory_::createHLineConstIterator(src, srcPos.x(), srcPos.y(), areaSize.width(), dataRect); typename _IteratorFactory_::HLineConstIterator khitSrc = _IteratorFactory_::createHLineConstIterator(src, col - m_khalfWidth, row + m_khalfHeight, m_kw, dataRect); for (int prow = 0; prow < areaSize.height(); ++prow) { // reload cache from copy for (quint32 i = 0; i < m_cacheSize; ++i) memcpy(m_pixelPtrCache[i], m_pixelPtrCacheCopy[i], channelCount * sizeof(qreal)); typename _IteratorFactory_::VLineConstIterator kitSrc = _IteratorFactory_::createVLineConstIterator(src, col + m_khalfWidth, row - m_khalfHeight, m_kh, dataRect); for (int pcol = 0; pcol < areaSize.width(); ++pcol) { // write original channel values memcpy(hitDst->rawData(), hitSrc->oldRawData(), m_pixelSize); convolveCache(hitDst->rawData()); ++col; kitSrc->nextColumn(); hitDst->nextPixel(); hitSrc->nextPixel(); moveKernelRight(kitSrc, m_pixelPtrCache); } row++; khitSrc->nextRow(); hitDst->nextRow(); hitSrc->nextRow(); col = srcPos.x(); moveKernelDown(khitSrc, m_pixelPtrCacheCopy); if (hasProgressUpdater) { this->m_progress->setValue(prow); if (this->m_progress->interrupted()) { cleanUp(); return; } } } } else /* if (traversingDirection == Vertical) */ { if(hasProgressUpdater) { this->m_progress->setRange(0, areaSize.width()); } typename _IteratorFactory_::VLineIterator vitDst = _IteratorFactory_::createVLineIterator(this->m_painter->device(), dstPos.x(), dstPos.y(), areaSize.height(), dataRect); typename _IteratorFactory_::VLineConstIterator vitSrc = _IteratorFactory_::createVLineConstIterator(src, srcPos.x(), srcPos.y(), areaSize.height(), dataRect); typename _IteratorFactory_::VLineConstIterator kitSrc = _IteratorFactory_::createVLineConstIterator(src, col + m_khalfWidth, row - m_khalfHeight, m_kh, dataRect); for (int pcol = 0; pcol < areaSize.width(); pcol++) { // reload cache from copy for (quint32 i = 0; i < m_cacheSize; ++i) memcpy(m_pixelPtrCache[i], m_pixelPtrCacheCopy[i], channelCount * sizeof(qreal)); typename _IteratorFactory_::HLineConstIterator khitSrc = _IteratorFactory_::createHLineConstIterator(src, col - m_khalfWidth, row + m_khalfHeight, m_kw, dataRect); for (int prow = 0; prow < areaSize.height(); prow++) { // write original channel values memcpy(vitDst->rawData(), vitSrc->oldRawData(), m_pixelSize); convolveCache(vitDst->rawData()); ++row; khitSrc->nextRow(); vitDst->nextPixel(); vitSrc->nextPixel(); moveKernelDown(khitSrc, m_pixelPtrCache); } ++col; kitSrc->nextColumn(); vitDst->nextColumn(); vitSrc->nextColumn(); row = srcPos.y(); moveKernelRight(kitSrc, m_pixelPtrCacheCopy); if (hasProgressUpdater) { this->m_progress->setValue(pcol); if (this->m_progress->interrupted()) { cleanUp(); return; } } } } cleanUp(); } inline void limitValue(qreal *value, qreal lowBound, qreal highBound) { if (*value > highBound) { *value = highBound; } else if (!(*value >= lowBound)) { // value < lowBound or value == NaN // IEEE compliant comparisons with NaN are always false *value = lowBound; } } template inline qreal convolveOneChannelFromCache(quint8* dstPtr, quint32 channel, qreal additionalMultiplier = 0.0) { qreal interimConvoResult = 0; for (quint32 pIndex = 0; pIndex < m_cacheSize; ++pIndex) { qreal cacheValue = m_pixelPtrCache[pIndex][channel]; interimConvoResult += m_kernelData[m_cacheSize - pIndex - 1] * cacheValue; } qreal channelPixelValue; if (additionalMultiplierActive) { channelPixelValue = (interimConvoResult * m_kernelFactor) * additionalMultiplier + m_absoluteOffset[channel]; } else { channelPixelValue = interimConvoResult * m_kernelFactor + m_absoluteOffset[channel]; } limitValue(&channelPixelValue, m_minClamp[channel], m_maxClamp[channel]); const quint32 channelPos = m_convChannelList[channel]->pos(); m_fromDoubleFuncPtr[channel](dstPtr, channelPos, channelPixelValue); return channelPixelValue; } inline void convolveCache(quint8* dstPtr) { if (m_alphaCachePos >= 0) { qreal alphaValue = convolveOneChannelFromCache(dstPtr, m_alphaCachePos); // TODO: we need a special case for applying LoG filter, // when the alpha i suniform and therefore should not be // filtered! //alphaValue = 255.0; if (alphaValue != 0.0) { qreal alphaValueInv = 1.0 / alphaValue; for (quint32 k = 0; k < m_convolveChannelsNo; ++k) { if (k == (quint32)m_alphaCachePos) continue; convolveOneChannelFromCache(dstPtr, k, alphaValueInv); } } else { for (quint32 k = 0; k < m_convolveChannelsNo; ++k) { if (k == (quint32)m_alphaCachePos) continue; const qreal zeroValue = 0.0; const quint32 channelPos = m_convChannelList[k]->pos(); m_fromDoubleFuncPtr[k](dstPtr, channelPos, zeroValue); } } } else { for (quint32 k = 0; k < m_convolveChannelsNo; ++k) { convolveOneChannelFromCache(dstPtr, k); } } } inline void moveKernelRight(typename _IteratorFactory_::VLineConstIterator& kitSrc, qreal **pixelPtrCache) { qreal** d = pixelPtrCache; for (quint32 krow = 0; krow < m_kh; ++krow) { qreal* first = *d; memmove(d, d + 1, (m_kw - 1) * sizeof(qreal *)); *(d + m_kw - 1) = first; d += m_kw; } qint32 i = m_kw - 1; do { const quint8* data = kitSrc->oldRawData(); loadPixelToCache(pixelPtrCache, data, i); i += m_kw; } while (kitSrc->nextPixel()); } inline void moveKernelDown(typename _IteratorFactory_::HLineConstIterator& kitSrc, qreal **pixelPtrCache) { quint8 **tmp = new quint8*[m_kw]; memcpy(tmp, pixelPtrCache, m_kw * sizeof(qreal *)); memmove(pixelPtrCache, pixelPtrCache + m_kw, (m_kw * m_kh - m_kw) * sizeof(quint8 *)); memcpy(pixelPtrCache + m_kw *(m_kh - 1), tmp, m_kw * sizeof(quint8 *)); delete[] tmp; qint32 i = m_kw * (m_kh - 1); do { const quint8* data = kitSrc->oldRawData(); loadPixelToCache(pixelPtrCache, data, i); i++; } while (kitSrc->nextPixel()); } void cleanUp() { for (quint32 c = 0; c < m_cacheSize; ++c) { delete[] m_pixelPtrCache[c]; delete[] m_pixelPtrCacheCopy[c]; } delete[] m_kernelData; delete[] m_pixelPtrCache; delete[] m_pixelPtrCacheCopy; delete[] m_minClamp; delete[] m_maxClamp; delete[] m_absoluteOffset; } private: quint32 m_kw, m_kh; quint32 m_khalfWidth, m_khalfHeight; quint32 m_convolveChannelsNo; quint32 m_cacheSize, m_pixelSize; int m_alphaCachePos; int m_alphaRealPos; qreal *m_kernelData; qreal** m_pixelPtrCache, ** m_pixelPtrCacheCopy; qreal* m_minClamp, *m_maxClamp, *m_absoluteOffset; qreal m_kernelFactor; QList m_convChannelList; QVector m_toDoubleFuncPtr; QVector m_fromDoubleFuncPtr; }; #endif diff --git a/libs/image/kis_edge_detection_kernel.cpp b/libs/image/kis_edge_detection_kernel.cpp index 839c88d07d..2596140852 100644 --- a/libs/image/kis_edge_detection_kernel.cpp +++ b/libs/image/kis_edge_detection_kernel.cpp @@ -1,428 +1,428 @@ /* * 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 analogue to + * 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); 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); 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); 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); 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); 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_fast_math.cpp b/libs/image/kis_fast_math.cpp index 4b6fe8df74..980f14cbce 100644 --- a/libs/image/kis_fast_math.cpp +++ b/libs/image/kis_fast_math.cpp @@ -1,132 +1,132 @@ /* * 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. * - * adopted from here http://www.snippetcenter.org/en/a-fast-atan2-function-s1868.aspx + * adopted from here http://web.archive.org/web/20090728150504/http://www.snippetcenter.org/en/a-fast-atan2-function-s1868.aspx */ #include "kis_fast_math.h" #include #include #include #include #include -// Algorithm from http://www.snippetcenter.org/en/a-fast-atan2-function-s1868.aspx +// Algorithm from http://web.archive.org/web/20090728150504/http://www.snippetcenter.org/en/a-fast-atan2-function-s1868.aspx const qreal MAX_SECOND_DERIV_IN_RANGE = 0.6495; /// precision const qreal MAX_ERROR = 0.0001; struct KisATanTable { KisATanTable() { qreal nf = ::sqrt(MAX_SECOND_DERIV_IN_RANGE / (8 * MAX_ERROR)); NUM_ATAN_ENTRIES = int(nf) + 1; // Build table qreal y = 10.0; qreal x; ATanTable = new qreal[NUM_ATAN_ENTRIES + 1]; ATanTable[0] = 0.0; for (quint32 i = 1; i <= NUM_ATAN_ENTRIES; i++) { x = (y / i) * NUM_ATAN_ENTRIES; ATanTable[i] = (qreal)::atan2(y, x); } } ~KisATanTable() { delete [] ATanTable; } quint32 NUM_ATAN_ENTRIES; qreal* ATanTable; }; Q_GLOBAL_STATIC(KisATanTable, kisATanTable) /// private functions inline qreal interp(qreal r, qreal a, qreal b) { return r*(b - a) + a; } inline qreal calcAngle(qreal x, qreal y) { static qreal af = kisATanTable->NUM_ATAN_ENTRIES; static int ai = kisATanTable->NUM_ATAN_ENTRIES; static qreal* ATanTable = kisATanTable->ATanTable; qreal di = (y / x) * af; int i = (int)(di); if (i >= ai) return ::atan2(y, x); return interp(di - i, ATanTable[i], ATanTable[i+1]); } qreal KisFastMath::atan2(qreal y, qreal x) { if (y == 0.0) { // the line is horizontal if (x >= 0.0) { // towards the right return(0.0);// the angle is 0 } // toward the left return qreal(M_PI); } // we now know that y is not 0 check x if (x == 0.0) { // the line is vertical if (y > 0.0) { return M_PI_2; } return -M_PI_2; } // from here on we know that niether x nor y is 0 if (x > 0.0) { // we are in quadrant 1 or 4 if (y > 0.0) { // we are in quadrant 1 // now figure out which side of the 45 degree line if (x > y) { return(calcAngle(x, y)); } return(M_PI_2 - calcAngle(y, x)); } // we are in quadrant 4 y = -y; // now figure out which side of the 45 degree line if (x > y) { return(-calcAngle(x, y)); } return(-M_PI_2 + calcAngle(y, x)); } // we are in quadrant 2 or 3 x = -x; // flip x so we can use it as a positive if (y > 0.0) { // we are in quadrant 2 // now figure out which side of the 45 degree line if (x > y) { return(M_PI - calcAngle(x, y)); } return(M_PI_2 + calcAngle(y, x)); } // we are in quadrant 3 y = -y; // flip y so we can use it as a positive // now figure out which side of the 45 degree line if (x > y) { return(-M_PI + calcAngle(x, y)); } return(-M_PI_2 - calcAngle(y, x)); } diff --git a/libs/image/kis_fast_math.h b/libs/image/kis_fast_math.h index a5f88008d3..fbf626a381 100644 --- a/libs/image/kis_fast_math.h +++ b/libs/image/kis_fast_math.h @@ -1,38 +1,38 @@ /* * 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. * - * adopted from here http://www.snippetcenter.org/en/a-fast-atan2-function-s1868.aspx + * adopted from here http://web.archive.org/web/20090728150504/http://www.snippetcenter.org/en/a-fast-atan2-function-s1868.aspx */ #ifndef _KIS_IMAGE_FAST_ #define _KIS_IMAGE_FAST_ #include #include "kritaimage_export.h" /** * This namespace contains fast but inaccurate version of mathematical function. */ namespace KisFastMath { /// atan2 replacement KRITAIMAGE_EXPORT qreal atan2(qreal y, qreal x); } #endif diff --git a/libs/image/kis_gaussian_kernel.cpp b/libs/image/kis_gaussian_kernel.cpp index d6966f1d49..1770ac00ff 100644 --- a/libs/image/kis_gaussian_kernel.cpp +++ b/libs/image/kis_gaussian_kernel.cpp @@ -1,400 +1,401 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_gaussian_kernel.h" #include "kis_global.h" #include "kis_convolution_kernel.h" #include #include #include qreal KisGaussianKernel::sigmaFromRadius(qreal radius) { return 0.3 * radius + 0.3; } int KisGaussianKernel::kernelSizeFromRadius(qreal radius) { return 6 * ceil(sigmaFromRadius(radius)) + 1; } Eigen::Matrix KisGaussianKernel::createHorizontalMatrix(qreal radius) { int kernelSize = kernelSizeFromRadius(radius); Eigen::Matrix matrix(1, kernelSize); const qreal sigma = sigmaFromRadius(radius); const qreal multiplicand = 1 / (sqrt(2 * M_PI * sigma * sigma)); const qreal exponentMultiplicand = 1 / (2 * sigma * sigma); /** * The kernel size should always be odd, then the position of the * central pixel can be easily calculated */ KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1); const int center = kernelSize / 2; for (int x = 0; x < kernelSize; x++) { qreal xDistance = center - x; matrix(0, x) = multiplicand * exp( -xDistance * xDistance * exponentMultiplicand ); } return matrix; } Eigen::Matrix KisGaussianKernel::createVerticalMatrix(qreal radius) { int kernelSize = kernelSizeFromRadius(radius); Eigen::Matrix matrix(kernelSize, 1); const qreal sigma = sigmaFromRadius(radius); const qreal multiplicand = 1 / (sqrt(2 * M_PI * sigma * sigma)); const qreal exponentMultiplicand = 1 / (2 * sigma * sigma); /** * The kernel size should always be odd, then the position of the * central pixel can be easily calculated */ KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1); const int center = kernelSize / 2; for (int y = 0; y < kernelSize; y++) { qreal yDistance = center - y; matrix(y, 0) = multiplicand * exp( -yDistance * yDistance * exponentMultiplicand ); } return matrix; } KisConvolutionKernelSP KisGaussianKernel::createHorizontalKernel(qreal radius) { Eigen::Matrix matrix = createHorizontalMatrix(radius); return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum()); } KisConvolutionKernelSP KisGaussianKernel::createVerticalKernel(qreal radius) { Eigen::Matrix matrix = createVerticalMatrix(radius); return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum()); } KisConvolutionKernelSP KisGaussianKernel::createUniform2DKernel(qreal xRadius, qreal yRadius) { Eigen::Matrix h = createHorizontalMatrix(xRadius); Eigen::Matrix v = createVerticalMatrix(yRadius); Eigen::Matrix uni = v * h; return KisConvolutionKernel::fromMatrix(uni, 0, uni.sum()); } void KisGaussianKernel::applyGaussian(KisPaintDeviceSP device, const QRect& rect, qreal xRadius, qreal yRadius, const QBitArray &channelFlags, KoUpdater *progressUpdater, - bool createTransaction) + bool createTransaction, + KisConvolutionBorderOp borderOp) { QPoint srcTopLeft = rect.topLeft(); if (KisConvolutionPainter::supportsFFTW()) { KisConvolutionPainter painter(device, KisConvolutionPainter::FFTW); painter.setChannelFlags(channelFlags); painter.setProgress(progressUpdater); KisConvolutionKernelSP kernel2D = KisGaussianKernel::createUniform2DKernel(xRadius, yRadius); QScopedPointer transaction; if (createTransaction && painter.needsTransaction(kernel2D)) { transaction.reset(new KisTransaction(device)); } - painter.applyMatrix(kernel2D, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); + painter.applyMatrix(kernel2D, device, srcTopLeft, srcTopLeft, rect.size(), borderOp); } else if (xRadius > 0.0 && yRadius > 0.0) { KisPaintDeviceSP interm = new KisPaintDevice(device->colorSpace()); interm->prepareClone(device); KisConvolutionKernelSP kernelHoriz = KisGaussianKernel::createHorizontalKernel(xRadius); KisConvolutionKernelSP kernelVertical = KisGaussianKernel::createVerticalKernel(yRadius); qreal verticalCenter = qreal(kernelVertical->height()) / 2.0; KisConvolutionPainter horizPainter(interm); horizPainter.setChannelFlags(channelFlags); horizPainter.setProgress(progressUpdater); horizPainter.applyMatrix(kernelHoriz, device, srcTopLeft - QPoint(0, ceil(verticalCenter)), srcTopLeft - QPoint(0, ceil(verticalCenter)), - rect.size() + QSize(0, 2 * ceil(verticalCenter)), BORDER_REPEAT); + rect.size() + QSize(0, 2 * ceil(verticalCenter)), borderOp); KisConvolutionPainter verticalPainter(device); verticalPainter.setChannelFlags(channelFlags); verticalPainter.setProgress(progressUpdater); - verticalPainter.applyMatrix(kernelVertical, interm, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); + verticalPainter.applyMatrix(kernelVertical, interm, srcTopLeft, srcTopLeft, rect.size(), borderOp); } else if (xRadius > 0.0) { KisConvolutionPainter painter(device); painter.setChannelFlags(channelFlags); painter.setProgress(progressUpdater); KisConvolutionKernelSP kernelHoriz = KisGaussianKernel::createHorizontalKernel(xRadius); QScopedPointer transaction; if (createTransaction && painter.needsTransaction(kernelHoriz)) { transaction.reset(new KisTransaction(device)); } - painter.applyMatrix(kernelHoriz, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); + painter.applyMatrix(kernelHoriz, device, srcTopLeft, srcTopLeft, rect.size(), borderOp); } else if (yRadius > 0.0) { KisConvolutionPainter painter(device); painter.setChannelFlags(channelFlags); painter.setProgress(progressUpdater); KisConvolutionKernelSP kernelVertical = KisGaussianKernel::createVerticalKernel(yRadius); QScopedPointer transaction; if (createTransaction && painter.needsTransaction(kernelVertical)) { transaction.reset(new KisTransaction(device)); } - painter.applyMatrix(kernelVertical, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); + painter.applyMatrix(kernelVertical, device, srcTopLeft, srcTopLeft, rect.size(), borderOp); } } Eigen::Matrix KisGaussianKernel::createLoGMatrix(qreal radius, qreal coeff, bool zeroCentered, bool includeWrappedArea) { int kernelSize = 2 * (includeWrappedArea ? 2 : 1) * std::ceil(radius) + 1; Eigen::Matrix matrix(kernelSize, kernelSize); const qreal sigma = radius/* / sqrt(2)*/; const qreal multiplicand = -1.0 / (M_PI * pow2(pow2(sigma))); const qreal exponentMultiplicand = 1 / (2 * pow2(sigma)); /** * The kernel size should always be odd, then the position of the * central pixel can be easily calculated */ KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1); const int center = kernelSize / 2; for (int y = 0; y < kernelSize; y++) { const qreal yDistance = center - y; for (int x = 0; x < kernelSize; x++) { const qreal xDistance = center - x; const qreal distance = pow2(xDistance) + pow2(yDistance); const qreal normalizedDistance = exponentMultiplicand * distance; matrix(x, y) = multiplicand * (1.0 - normalizedDistance) * exp(-normalizedDistance); } } qreal lateral = matrix.sum() - matrix(center, center); matrix(center, center) = -lateral; qreal totalSum = 0; if (zeroCentered) { for (int y = 0; y < kernelSize; y++) { for (int x = 0; x < kernelSize; x++) { const qreal value = matrix(x, y); totalSum += value; } } } qreal positiveSum = 0; qreal sideSum = 0; qreal quarterSum = 0; totalSum = 0; const qreal offset = totalSum / pow2(qreal(kernelSize)); for (int y = 0; y < kernelSize; y++) { for (int x = 0; x < kernelSize; x++) { qreal value = matrix(x, y); value -= offset; matrix(x, y) = value; if (value > 0) { positiveSum += value; } if (x > center) { sideSum += value; } if (x > center && y > center) { quarterSum += value; } totalSum += value; } } const qreal scale = coeff * 2.0 / positiveSum; matrix *= scale; positiveSum *= scale; sideSum *= scale; quarterSum *= scale; //qDebug() << ppVar(positiveSum) << ppVar(sideSum) << ppVar(quarterSum); return matrix; } void KisGaussianKernel::applyLoG(KisPaintDeviceSP device, const QRect& rect, qreal radius, qreal coeff, const QBitArray &channelFlags, KoUpdater *progressUpdater) { QPoint srcTopLeft = rect.topLeft(); KisConvolutionPainter painter(device); painter.setChannelFlags(channelFlags); painter.setProgress(progressUpdater); Eigen::Matrix matrix = createLoGMatrix(radius, coeff, false, true); KisConvolutionKernelSP kernel = KisConvolutionKernel::fromMatrix(matrix, 0, 0); painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); } void KisGaussianKernel::applyTightLoG(KisPaintDeviceSP device, const QRect& rect, qreal radius, qreal coeff, const QBitArray &channelFlags, KoUpdater *progressUpdater) { QPoint srcTopLeft = rect.topLeft(); KisConvolutionPainter painter(device); painter.setChannelFlags(channelFlags); painter.setProgress(progressUpdater); Eigen::Matrix matrix = createLoGMatrix(radius, coeff, true, false); KisConvolutionKernelSP kernel = KisConvolutionKernel::fromMatrix(matrix, 0, 0); painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); } Eigen::Matrix KisGaussianKernel::createDilateMatrix(qreal radius) { const int kernelSize = 2 * std::ceil(radius) + 1; Eigen::Matrix matrix(kernelSize, kernelSize); const qreal fadeStart = qMax(1.0, radius - 1.0); /** * The kernel size should always be odd, then the position of the * central pixel can be easily calculated */ KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1); const int center = kernelSize / 2; for (int y = 0; y < kernelSize; y++) { const qreal yDistance = center - y; for (int x = 0; x < kernelSize; x++) { const qreal xDistance = center - x; const qreal distance = std::sqrt(pow2(xDistance) + pow2(yDistance)); qreal value = 1.0; if (distance > radius + 1e-3) { value = 0.0; } else if (distance > fadeStart) { value = qMax(0.0, radius - distance); } matrix(x, y) = value; } } return matrix; } void KisGaussianKernel::applyDilate(KisPaintDeviceSP device, const QRect &rect, qreal radius, const QBitArray &channelFlags, KoUpdater *progressUpdater, bool createTransaction) { KIS_SAFE_ASSERT_RECOVER_RETURN(device->colorSpace()->pixelSize() == 1); QPoint srcTopLeft = rect.topLeft(); KisConvolutionPainter painter(device); painter.setChannelFlags(channelFlags); painter.setProgress(progressUpdater); Eigen::Matrix matrix = createDilateMatrix(radius); KisConvolutionKernelSP kernel = KisConvolutionKernel::fromMatrix(matrix, 0, 1.0); QScopedPointer transaction; if (createTransaction && painter.needsTransaction(kernel)) { transaction.reset(new KisTransaction(device)); } painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); } #include "kis_sequential_iterator.h" void KisGaussianKernel::applyErodeU8(KisPaintDeviceSP device, const QRect &rect, qreal radius, const QBitArray &channelFlags, KoUpdater *progressUpdater, bool createTransaction) { KIS_SAFE_ASSERT_RECOVER_RETURN(device->colorSpace()->pixelSize() == 1); { KisSequentialIterator dstIt(device, rect); while (dstIt.nextPixel()) { quint8 *dstPtr = dstIt.rawData(); *dstPtr = 255 - *dstPtr; } } applyDilate(device, rect, radius, channelFlags, progressUpdater, createTransaction); { KisSequentialIterator dstIt(device, rect); while (dstIt.nextPixel()) { quint8 *dstPtr = dstIt.rawData(); *dstPtr = 255 - *dstPtr; } } } diff --git a/libs/image/kis_gaussian_kernel.h b/libs/image/kis_gaussian_kernel.h index d1ae29e8c4..725a647bc4 100644 --- a/libs/image/kis_gaussian_kernel.h +++ b/libs/image/kis_gaussian_kernel.h @@ -1,90 +1,92 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_GAUSSIAN_KERNEL_H #define __KIS_GAUSSIAN_KERNEL_H #include "kritaimage_export.h" #include "kis_types.h" +#include "kis_convolution_painter.h" #include class QRect; class KRITAIMAGE_EXPORT KisGaussianKernel { public: static Eigen::Matrix createHorizontalMatrix(qreal radius); static Eigen::Matrix createVerticalMatrix(qreal radius); static KisConvolutionKernelSP createHorizontalKernel(qreal radius); static KisConvolutionKernelSP createVerticalKernel(qreal radius); static KisConvolutionKernelSP createUniform2DKernel(qreal xRadius, qreal yRadius); static qreal sigmaFromRadius(qreal radius); static int kernelSizeFromRadius(qreal radius); static void applyGaussian(KisPaintDeviceSP device, const QRect& rect, qreal xRadius, qreal yRadius, const QBitArray &channelFlags, KoUpdater *updater, - bool createTransaction = false); + bool createTransaction = false, + KisConvolutionBorderOp borderOp = BORDER_REPEAT); static Eigen::Matrix createLoGMatrix(qreal radius, qreal coeff, bool zeroCentered, bool includeWrappedArea); static void applyLoG(KisPaintDeviceSP device, const QRect& rect, qreal radius, qreal coeff, const QBitArray &channelFlags, KoUpdater *progressUpdater); static void applyTightLoG(KisPaintDeviceSP device, const QRect& rect, qreal radius, qreal coeff, const QBitArray &channelFlags, KoUpdater *progressUpdater); static Eigen::Matrix createDilateMatrix(qreal radius); static void applyDilate(KisPaintDeviceSP device, const QRect& rect, qreal radius, const QBitArray &channelFlags, KoUpdater *progressUpdater, bool createTransaction = false); static void applyErodeU8(KisPaintDeviceSP device, const QRect& rect, qreal radius, const QBitArray &channelFlags, KoUpdater *progressUpdater, bool createTransaction = false); }; #endif /* __KIS_GAUSSIAN_KERNEL_H */ diff --git a/libs/image/kis_keyframe.cpp b/libs/image/kis_keyframe.cpp index cb230cc17c..3f200bbab2 100644 --- a/libs/image/kis_keyframe.cpp +++ b/libs/image/kis_keyframe.cpp @@ -1,132 +1,133 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image_config.h" #include "kis_keyframe.h" #include "kis_keyframe_channel.h" #include "kis_types.h" #include struct KisKeyframeSPStaticRegistrar { KisKeyframeSPStaticRegistrar() { qRegisterMetaType("KisKeyframeSP"); } }; static KisKeyframeSPStaticRegistrar __registrar; struct KisKeyframe::Private { QPointer channel; int time; - InterpolationMode interpolationMode; - InterpolationTangentsMode tangentsMode; + InterpolationMode interpolationMode {InterpolationMode::Constant}; + InterpolationTangentsMode tangentsMode {InterpolationTangentsMode::Smooth}; QPointF leftTangent; QPointF rightTangent; int colorLabel{0}; Private(KisKeyframeChannel *channel, int time) - : channel(channel), time(time), interpolationMode(Constant) + : channel(channel) + , time(time) {} }; KisKeyframe::KisKeyframe(KisKeyframeChannel *channel, int time) : m_d(new Private(channel, time)) { m_d->colorLabel = KisImageConfig(true).defaultFrameColorLabel(); } KisKeyframe::KisKeyframe(const KisKeyframe *rhs, KisKeyframeChannel *channel) : m_d(new Private(channel, rhs->time())) { m_d->interpolationMode = rhs->m_d->interpolationMode; m_d->tangentsMode = rhs->m_d->tangentsMode; m_d->leftTangent = rhs->m_d->leftTangent; m_d->rightTangent = rhs->m_d->rightTangent; m_d->colorLabel = rhs->m_d->colorLabel; } KisKeyframe::~KisKeyframe() {} int KisKeyframe::time() const { return m_d->time; } void KisKeyframe::setTime(int time) { m_d->time = time; } void KisKeyframe::setInterpolationMode(KisKeyframe::InterpolationMode mode) { m_d->interpolationMode = mode; } KisKeyframe::InterpolationMode KisKeyframe::interpolationMode() const { return m_d->interpolationMode; } void KisKeyframe::setTangentsMode(KisKeyframe::InterpolationTangentsMode mode) { m_d->tangentsMode = mode; } KisKeyframe::InterpolationTangentsMode KisKeyframe::tangentsMode() const { return m_d->tangentsMode; } void KisKeyframe::setInterpolationTangents(QPointF leftTangent, QPointF rightTangent) { m_d->leftTangent = leftTangent; m_d->rightTangent = rightTangent; } QPointF KisKeyframe::leftTangent() const { return m_d->leftTangent; } QPointF KisKeyframe::rightTangent() const { return m_d->rightTangent; } int KisKeyframe::colorLabel() const { return m_d->colorLabel; } void KisKeyframe::setColorLabel(int label) { m_d->colorLabel = label; } bool KisKeyframe::hasContent() const { return true; } KisKeyframeChannel *KisKeyframe::channel() const { return m_d->channel; } diff --git a/libs/image/kis_onion_skin_compositor.cpp b/libs/image/kis_onion_skin_compositor.cpp index 9e408005e7..b96609819c 100644 --- a/libs/image/kis_onion_skin_compositor.cpp +++ b/libs/image/kis_onion_skin_compositor.cpp @@ -1,227 +1,237 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_onion_skin_compositor.h" #include "kis_paint_device.h" #include "kis_painter.h" #include "KoColor.h" #include "KoColorSpace.h" #include "KoCompositeOpRegistry.h" #include "KoColorSpaceConstants.h" #include "kis_image_config.h" #include "kis_raster_keyframe_channel.h" Q_GLOBAL_STATIC(KisOnionSkinCompositor, s_instance) struct KisOnionSkinCompositor::Private { int numberOfSkins = 0; int tintFactor = 0; QColor backwardTintColor; QColor forwardTintColor; QVector backwardOpacities; QVector forwardOpacities; int configSeqNo = 0; QList colorLabelFilter; int skinOpacity(int offset) { const QVector &bo = backwardOpacities; const QVector &fo = forwardOpacities; return offset > 0 ? fo[qAbs(offset) - 1] : bo[qAbs(offset) - 1]; } KisPaintDeviceSP setUpTintDevice(const QColor &tintColor, const KoColorSpace *colorSpace) { KisPaintDeviceSP tintDevice = new KisPaintDevice(colorSpace); KoColor color = KoColor(tintColor, colorSpace); tintDevice->setDefaultPixel(color); return tintDevice; } KisKeyframeSP getNextFrameToComposite(KisKeyframeChannel *channel, KisKeyframeSP keyframe, bool backwards) { while (!keyframe.isNull()) { keyframe = backwards ? channel->previousKeyframe(keyframe) : channel->nextKeyframe(keyframe); if (colorLabelFilter.isEmpty()) { return keyframe; } else if (!keyframe.isNull()) { if (colorLabelFilter.contains(keyframe->colorLabel())) { return keyframe; } } } return keyframe; } void tryCompositeFrame(KisRasterKeyframeChannel *keyframes, KisKeyframeSP keyframe, KisPainter &gcFrame, KisPainter &gcDest, KisPaintDeviceSP tintSource, int opacity, const QRect &rect) { if (keyframe.isNull() || opacity == OPACITY_TRANSPARENT_U8) return; keyframes->fetchFrame(keyframe, gcFrame.device()); gcFrame.bitBlt(rect.topLeft(), tintSource, rect); gcDest.setOpacity(opacity); gcDest.bitBlt(rect.topLeft(), gcFrame.device(), rect); } void refreshConfig() { KisImageConfig config(true); numberOfSkins = config.numberOfOnionSkins(); tintFactor = config.onionSkinTintFactor(); backwardTintColor = config.onionSkinTintColorBackward(); forwardTintColor = config.onionSkinTintColorForward(); backwardOpacities.resize(numberOfSkins); forwardOpacities.resize(numberOfSkins); const int mainState = (int) config.onionSkinState(0); const qreal scaleFactor = mainState * config.onionSkinOpacity(0) / 255.0; for (int i = 0; i < numberOfSkins; i++) { int backwardState = (int) config.onionSkinState(-(i + 1)); int forwardState = (int) config.onionSkinState(i + 1); backwardOpacities[i] = scaleFactor * backwardState * config.onionSkinOpacity(-(i + 1)); forwardOpacities[i] = scaleFactor * forwardState * config.onionSkinOpacity(i + 1); } configSeqNo++; } }; KisOnionSkinCompositor *KisOnionSkinCompositor::instance() { return s_instance; } KisOnionSkinCompositor::KisOnionSkinCompositor() : m_d(new Private) { m_d->refreshConfig(); } KisOnionSkinCompositor::~KisOnionSkinCompositor() {} int KisOnionSkinCompositor::configSeqNo() const { return m_d->configSeqNo; } void KisOnionSkinCompositor::setColorLabelFilter(QList colors) { m_d->colorLabelFilter = colors; } void KisOnionSkinCompositor::composite(const KisPaintDeviceSP sourceDevice, KisPaintDeviceSP targetDevice, const QRect& rect) { KisRasterKeyframeChannel *keyframes = sourceDevice->keyframeChannel(); KisPaintDeviceSP frameDevice = new KisPaintDevice(sourceDevice->colorSpace()); KisPainter gcFrame(frameDevice); QBitArray channelFlags = targetDevice->colorSpace()->channelFlags(true, false); gcFrame.setChannelFlags(channelFlags); gcFrame.setOpacity(m_d->tintFactor); KisPaintDeviceSP backwardTintDevice = m_d->setUpTintDevice(m_d->backwardTintColor, sourceDevice->colorSpace()); KisPaintDeviceSP forwardTintDevice = m_d->setUpTintDevice(m_d->forwardTintColor, sourceDevice->colorSpace()); KisPainter gcDest(targetDevice); gcDest.setCompositeOp(sourceDevice->colorSpace()->compositeOp(COMPOSITE_BEHIND)); KisKeyframeSP keyframeBck; KisKeyframeSP keyframeFwd; int time = sourceDevice->defaultBounds()->currentTime(); + + if (!keyframes) { // it happens when you try to show onion skins on non-animated layer with opacity keyframes + return; + } + keyframeBck = keyframeFwd = keyframes->activeKeyframeAt(time); for (int offset = 1; offset <= m_d->numberOfSkins; offset++) { keyframeBck = m_d->getNextFrameToComposite(keyframes, keyframeBck, true); keyframeFwd = m_d->getNextFrameToComposite(keyframes, keyframeFwd, false); if (!keyframeBck.isNull()) { m_d->tryCompositeFrame(keyframes, keyframeBck, gcFrame, gcDest, backwardTintDevice, m_d->skinOpacity(-offset), rect); } if (!keyframeFwd.isNull()) { m_d->tryCompositeFrame(keyframes, keyframeFwd, gcFrame, gcDest, forwardTintDevice, m_d->skinOpacity(offset), rect); } } } QRect KisOnionSkinCompositor::calculateFullExtent(const KisPaintDeviceSP device) { QRect rect; KisRasterKeyframeChannel *channel = device->keyframeChannel(); if (!channel) return rect; KisKeyframeSP keyframe = channel->firstKeyframe(); while (keyframe) { rect |= channel->frameExtents(keyframe); keyframe = channel->nextKeyframe(keyframe); } return rect; } QRect KisOnionSkinCompositor::calculateExtent(const KisPaintDeviceSP device) { QRect rect; KisKeyframeSP keyframeBck; KisKeyframeSP keyframeFwd; KisRasterKeyframeChannel *channel = device->keyframeChannel(); + + if (!channel) { // it happens when you try to show onion skins on non-animated layer with opacity keyframes + return rect; + } + keyframeBck = keyframeFwd = channel->activeKeyframeAt(device->defaultBounds()->currentTime()); for (int offset = 1; offset <= m_d->numberOfSkins; offset++) { if (!keyframeBck.isNull()) { keyframeBck = channel->previousKeyframe(keyframeBck); if (!keyframeBck.isNull()) { rect |= channel->frameExtents(keyframeBck); } } if (!keyframeFwd.isNull()) { keyframeFwd = channel->nextKeyframe(keyframeFwd); if (!keyframeFwd.isNull()) { rect |= channel->frameExtents(keyframeFwd); } } } return rect; } void KisOnionSkinCompositor::configChanged() { m_d->refreshConfig(); emit sigOnionSkinChanged(); } diff --git a/libs/image/kis_painter.cc b/libs/image/kis_painter.cc index 7e8cc21aef..473a69fade 100644 --- a/libs/image/kis_painter.cc +++ b/libs/image/kis_painter.cc @@ -1,3014 +1,3022 @@ /* * 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->pattern = 0; d->sourceLayer = 0; d->fillStyle = FillStyleNone; d->strokeStyle = StrokeStyleBrush; d->antiAliasPolygonFill = true; d->progressUpdater = 0; d->gradient = 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(); } 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; } 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) { /** * 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; // 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(srcX, srcY); KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(dstX, dstY); /* 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(dstX, dstY); 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(x, y); if(d->selection) { KisPaintDeviceSP selectionProjection(d->selection->projection()); KisRandomConstAccessorSP maskIt = selectionProjection->createRandomConstAccessorNG(x, y); 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); } 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) { // we are drawing mask, it has to be white // color of the path is given by paintColor() KIS_SAFE_ASSERT_RECOVER_NOOP(_pen.color() == Qt::white); 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(x1, y1); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1); } 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 = (float)yd / (float)xd; + 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(x, y); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x, y); } 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 = 1.0f / m; + 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(x1, y1); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1); } // 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(x1, y1); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1); } 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(start.x(), start.y()); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(start.x(), start.y()); } 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 KoPattern * pattern) { d->pattern = pattern; } const KoPattern * 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::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 KoAbstractGradient* gradient) { d->gradient = gradient; } const KoAbstractGradient* 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) const { KisLodTransform t(d->device); QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint(); KritaUtils::mirrorDab(direction, effectiveAxesCenter, dab); } 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_properties_configuration.h b/libs/image/kis_properties_configuration.h index 60ef3727e7..886d5c283b 100644 --- a/libs/image/kis_properties_configuration.h +++ b/libs/image/kis_properties_configuration.h @@ -1,224 +1,224 @@ /* * 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_PROPERTIES_CONFIGURATION_H_ #define _KIS_PROPERTIES_CONFIGURATION_H_ #include #include #include #include #include #include class QDomElement; class QDomDocument; #include "kis_serializable_configuration.h" #include "kritaimage_export.h" #include "kis_types.h" /** * KisPropertiesConfiguration is a map-based properties class that can * be serialized and deserialized. * * It differs from the base class KisSerializableConfiguration in that * it provides a number of convenience methods to get at the data and */ class KRITAIMAGE_EXPORT KisPropertiesConfiguration : public KisSerializableConfiguration { public: /** * Create a new properties config. */ KisPropertiesConfiguration(); ~KisPropertiesConfiguration() override; /** * Deep copy the properties \p rhs */ KisPropertiesConfiguration(const KisPropertiesConfiguration& rhs); /** * Deep copy the properties \p rhs */ KisPropertiesConfiguration& operator=(const KisPropertiesConfiguration& rhs); public: /** * Fill the properties configuration object from the XML encoded representation in s. * This function use the "Legacy" style XML of the 1.x .kra file format. * @param xml the string that will be parsed as xml * @param clear if true, the properties map will be emptied. * @return true is the xml document could be parsed */ bool fromXML(const QString& xml, bool clear = true) override; /** * Fill the properties configuration object from the XML encoded representation in s. * This function use the "Legacy" style XML of the 1.x .kra file format. * * Note: the existing properties will not be cleared */ void fromXML(const QDomElement&) override; /** * Create a serialized version of this properties config * This function use the "Legacy" style XML of the 1.x .kra file format. */ void toXML(QDomDocument&, QDomElement&) const override; /** * Create a serialized version of this properties config * This function use the "Legacy" style XML of the 1.x .kra file format. */ QString toXML() const override; /** * @return true if the map contains a property with the specified name */ virtual bool hasProperty(const QString& name) const; /** * Set the property with name to value. */ virtual void setProperty(const QString & name, const QVariant & value); /** * Set value to the value associated with property name * * XXX: API alert: a setter that is prefixed with get? * * @return false if the specified property did not exist. */ virtual bool getProperty(const QString & name, QVariant & value) const; virtual QVariant getProperty(const QString & name) const; template T getPropertyLazy(const QString & name, const T &defaultValue) const { QVariant value = getProperty(name); return value.isValid() ? value.value() : defaultValue; } QString getPropertyLazy(const QString & name, const char *defaultValue) const { return getPropertyLazy(name, QString(defaultValue)); } int getInt(const QString & name, int def = 0) const; double getDouble(const QString & name, double def = 0.0) const; float getFloat(const QString& name, float def = 0.0) const; bool getBool(const QString & name, bool def = false) const; QString getString(const QString & name, const QString & def = QString()) const; KisCubicCurve getCubicCurve(const QString & name, const KisCubicCurve & curve = KisCubicCurve()) const; /** * @brief getColor fetch the given property as a KoColor. * * The color can be stored as *
    *
  • A KoColor *
  • A QColor *
  • A string that can be parsed as an XML color definition - *
  • A string that QColor can convert to a color (see http://doc.qt.io/qt-5/qcolor.html#setNamedColor) + *
  • A string that QColor can convert to a color (see https://doc.qt.io/qt-5/qcolor.html#setNamedColor) *
  • An integer that QColor can convert to a color *
* * @param name the name of the property * @param color the default value to be returned if the @param name does not exist. * @return returns the named property as a KoColor if the value can be converted to a color, * otherwise a empty KoColor is returned. */ KoColor getColor(const QString& name, const KoColor& color = KoColor()) const; QMap getProperties() const; /// Clear the map of properties void clearProperties(); /// Marks a property that should not be saved by toXML void setPropertyNotSaved(const QString & name); void removeProperty(const QString & name); /** * Get the keys of all the properties in the object */ virtual QList getPropertiesKeys() const; /** * Get a set of properties, which keys are prefixed with \p prefix. The settings object * \p config will have all these properties with the prefix stripped from them. */ void getPrefixedProperties(const QString &prefix, KisPropertiesConfiguration *config) const; /** * A convenience override */ void getPrefixedProperties(const QString &prefix, KisPropertiesConfigurationSP config) const; /** * Takes all the properties from \p config, adds \p prefix to all their keys and puths them * into this properties object */ void setPrefixedProperties(const QString &prefix, const KisPropertiesConfiguration *config); /** * A convenience override */ void setPrefixedProperties(const QString &prefix, const KisPropertiesConfigurationSP config); static QString escapeString(const QString &string); static QString unescapeString(const QString &string); void setProperty(const QString &name, const QStringList &value); QStringList getStringList(const QString &name, const QStringList &defaultValue = QStringList()) const; QStringList getPropertyLazy(const QString &name, const QStringList &defaultValue) const; public: void dump() const; private: struct Private; Private* const d; }; class KRITAIMAGE_EXPORT KisPropertiesConfigurationFactory : public KisSerializableConfigurationFactory { public: KisPropertiesConfigurationFactory(); ~KisPropertiesConfigurationFactory() override; KisSerializableConfigurationSP createDefault() override; KisSerializableConfigurationSP create(const QDomElement& e) override; private: struct Private; Private* const d; }; #endif diff --git a/libs/image/kis_psd_layer_style.h b/libs/image/kis_psd_layer_style.h index a536b19d8c..2711f296ff 100644 --- a/libs/image/kis_psd_layer_style.h +++ b/libs/image/kis_psd_layer_style.h @@ -1,101 +1,101 @@ /* * Copyright (c) 2014 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_PSD_LAYER_STYLE_H #define KIS_PSD_LAYER_STYLE_H class QIODevice; class QUuid; #include #include #include "kis_types.h" #include "kritaimage_export.h" class KisPSDLayerStyle; typedef QSharedPointer KisPSDLayerStyleSP; /** * @brief The KisPSDLayerStyle class implements loading, saving and applying * the PSD layer effects. * - * See http://www.tonton-pixel.com/Photoshop%20Additional%20File%20Formats/styles-file-format.html + * See https://www.tonton-pixel.com/Photoshop%20Additional%20File%20Formats/styles-file-format.html * */ class KRITAIMAGE_EXPORT KisPSDLayerStyle { public: explicit KisPSDLayerStyle(); virtual ~KisPSDLayerStyle(); KisPSDLayerStyle(const KisPSDLayerStyle& rhs); KisPSDLayerStyle operator=(const KisPSDLayerStyle& rhs); KisPSDLayerStyleSP clone() const; void clear(); QString name() const; void setName(const QString &value); QUuid uuid() const; void setUuid(const QUuid &value) const; QString psdUuid() const; void setPsdUuid(const QString &value) const; /** * \return true if all the styles are disabled */ bool isEmpty() const; bool isEnabled() const; void setEnabled(bool value); const psd_layer_effects_context* context() const; const psd_layer_effects_drop_shadow* dropShadow() const; const psd_layer_effects_inner_shadow* innerShadow() const; const psd_layer_effects_outer_glow* outerGlow() const; const psd_layer_effects_inner_glow* innerGlow() const; const psd_layer_effects_satin* satin() const; const psd_layer_effects_color_overlay* colorOverlay() const; const psd_layer_effects_gradient_overlay* gradientOverlay() const; const psd_layer_effects_pattern_overlay* patternOverlay() const; const psd_layer_effects_stroke* stroke() const; const psd_layer_effects_bevel_emboss* bevelAndEmboss() const; psd_layer_effects_context* context(); psd_layer_effects_drop_shadow* dropShadow(); psd_layer_effects_inner_shadow* innerShadow(); psd_layer_effects_outer_glow* outerGlow(); psd_layer_effects_inner_glow* innerGlow(); psd_layer_effects_satin* satin(); psd_layer_effects_color_overlay* colorOverlay(); psd_layer_effects_gradient_overlay* gradientOverlay(); psd_layer_effects_pattern_overlay* patternOverlay(); psd_layer_effects_stroke* stroke(); psd_layer_effects_bevel_emboss* bevelAndEmboss(); private: struct Private; Private * const d; }; #endif // KIS_PSD_LAYER_STYLE_H diff --git a/libs/image/kis_rect_mask_generator.cpp b/libs/image/kis_rect_mask_generator.cpp index 1d6200a39b..7b98fcad8f 100644 --- a/libs/image/kis_rect_mask_generator.cpp +++ b/libs/image/kis_rect_mask_generator.cpp @@ -1,158 +1,160 @@ /* * Copyright (c) 2004,2007,2008,2009.2010 Cyrille Berger * Copyright (c) 2018 Ivan Santa Maria * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include //MSVC requires that Vc come first #include #include #ifdef HAVE_VC #if defined(__clang__) #pragma GCC diagnostic ignored "-Wundef" #pragma GCC diagnostic ignored "-Wlocal-type-template-args" #endif #if defined _MSC_VER // Lets shut up the "possible loss of data" and "forcing value to bool 'true' or 'false' #pragma warning ( push ) #pragma warning ( disable : 4244 ) #pragma warning ( disable : 4800 ) #endif #include #include #if defined _MSC_VER #pragma warning ( pop ) #endif #endif #include #include "kis_fast_math.h" #include "kis_rect_mask_generator.h" #include "kis_rect_mask_generator_p.h" #include "kis_base_mask_generator.h" #include "kis_brush_mask_applicator_factories.h" #include "kis_brush_mask_applicator_base.h" #include KisRectangleMaskGenerator::KisRectangleMaskGenerator(qreal radius, qreal ratio, qreal fh, qreal fv, int spikes, bool antialiasEdges) : KisMaskGenerator(radius, ratio, fh, fv, spikes, antialiasEdges, RECTANGLE, DefaultId), d(new Private) { setScale(1.0, 1.0); // store the variable locally to allow vector implementation read it easily d->copyOfAntialiasEdges = antialiasEdges; d->applicator.reset(createOptimizedClass >(this)); } KisRectangleMaskGenerator::KisRectangleMaskGenerator(const KisRectangleMaskGenerator &rhs) : KisMaskGenerator(rhs), d(new Private(*rhs.d)) { d->applicator.reset(createOptimizedClass >(this)); } KisMaskGenerator* KisRectangleMaskGenerator::clone() const { return new KisRectangleMaskGenerator(*this); } KisRectangleMaskGenerator::~KisRectangleMaskGenerator() { } void KisRectangleMaskGenerator::setScale(qreal scaleX, qreal scaleY) { KisMaskGenerator::setScale(scaleX, scaleY); d->xcoeff = 2.0 / effectiveSrcWidth(); d->ycoeff = 2.0 / effectiveSrcHeight(); d->xfadecoeff = (horizontalFade() == 0) ? 1 : (2.0 / (horizontalFade() * effectiveSrcWidth())); d->yfadecoeff = (verticalFade() == 0) ? 1 : (2.0 / (verticalFade() * effectiveSrcHeight())); setSoftness(this->softness()); } void KisRectangleMaskGenerator::setSoftness(qreal softness) { KisMaskGenerator::setSoftness(softness); qreal safeSoftnessCoeff = qreal(1.0) / qMax(qreal(0.01), softness); d->transformedFadeX = d->xfadecoeff * safeSoftnessCoeff; d->transformedFadeY = d->yfadecoeff * safeSoftnessCoeff; } bool KisRectangleMaskGenerator::shouldSupersample() const { return effectiveSrcWidth() < 10 || effectiveSrcHeight() < 10; } bool KisRectangleMaskGenerator::shouldVectorize() const { return !shouldSupersample() && spikes() == 2; } KisBrushMaskApplicatorBase* KisRectangleMaskGenerator::applicator() { return d->applicator.data(); } void KisRectangleMaskGenerator::resetMaskApplicator(bool forceScalar) { d->applicator.reset(createOptimizedClass >(this,forceScalar)); } quint8 KisRectangleMaskGenerator::valueAt(qreal x, qreal y) const { if (isEmpty()) return 255; qreal xr = qAbs(x /*- m_xcenter*/); qreal yr = qAbs(y /*- m_ycenter*/); fixRotation(xr, yr); xr = qAbs(xr); yr = qAbs(yr); qreal nxr = xr * d->xcoeff; qreal nyr = yr * d->ycoeff; if (nxr > 1.0 || nyr > 1.0) return 255; if (antialiasEdges()) { xr += 1.0; yr += 1.0; } qreal fxr = xr * d->transformedFadeX; qreal fyr = yr * d->transformedFadeY; - int fxrInt = fxr * 1e4; - int fyrInt = fyr * 1e4; + qreal fxnorm = nxr * (fxr - 1.0) / (fxr - nxr); + qreal fynorm = nyr * (fyr - 1.0) / (fyr - nyr); - if (fxr > 1.0 && (fxrInt >= fyrInt || fyr < 1.0)) { - return 255 * nxr * (fxr - 1.0) / (fxr - nxr); - } + qreal retValue = 0; - if (fyr > 1.0 && (fyrInt > fxrInt || fxr < 1.0)) { - return 255 * nyr * (fyr - 1.0) / (fyr - nyr); - } + if(fxr > 1.0) { + retValue = fxnorm; + } + + if (fxnorm < fynorm && fyr > 1.0) { + retValue = fynorm; + } - return 0; + return retValue * 255; } diff --git a/libs/image/kis_selection_filters.cpp b/libs/image/kis_selection_filters.cpp index 5c2a4ebacc..dd80a72161 100644 --- a/libs/image/kis_selection_filters.cpp +++ b/libs/image/kis_selection_filters.cpp @@ -1,875 +1,875 @@ /* * Copyright (c) 2005 Michael Thaler * 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_selection_filters.h" #include #include #include "kis_convolution_painter.h" #include "kis_convolution_kernel.h" #include "kis_pixel_selection.h" #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define RINT(x) floor ((x) + 0.5) KisSelectionFilter::~KisSelectionFilter() { } KUndo2MagicString KisSelectionFilter::name() { return KUndo2MagicString(); } QRect KisSelectionFilter::changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) { Q_UNUSED(defaultBounds); return rect; } void KisSelectionFilter::computeBorder(qint32* circ, qint32 xradius, qint32 yradius) { qint32 i; qint32 diameter = xradius * 2 + 1; double tmp; for (i = 0; i < diameter; i++) { if (i > xradius) tmp = (i - xradius) - 0.5; else if (i < xradius) tmp = (xradius - i) - 0.5; else tmp = 0.0; - double divisor = (double) xradius * sqrt(xradius * xradius - tmp * tmp); + double divisor = (double) xradius; if (divisor == 0.0) { divisor = 1.0; } - circ[i] = (qint32) RINT(yradius / divisor); + circ[i] = (qint32) RINT(yradius * sqrt(xradius * xradius - tmp * tmp) / divisor); } } void KisSelectionFilter::rotatePointers(quint8** p, quint32 n) { quint32 i; quint8 *p0 = p[0]; for (i = 0; i < n - 1; i++) { p[i] = p[i + 1]; } p[i] = p0; } void KisSelectionFilter::computeTransition(quint8* transition, quint8** buf, qint32 width) { qint32 x = 0; if (width == 1) { if (buf[1][x] > 127 && (buf[0][x] < 128 || buf[2][x] < 128)) transition[x] = 255; else transition[x] = 0; return; } if (buf[1][x] > 127) { if (buf[0][x] < 128 || buf[0][x + 1] < 128 || buf[1][x + 1] < 128 || buf[2][x] < 128 || buf[2][x + 1] < 128) transition[x] = 255; else transition[x] = 0; } else transition[x] = 0; for (qint32 x = 1; x < width - 1; x++) { if (buf[1][x] >= 128) { if (buf[0][x - 1] < 128 || buf[0][x] < 128 || buf[0][x + 1] < 128 || buf[1][x - 1] < 128 || buf[1][x + 1] < 128 || buf[2][x - 1] < 128 || buf[2][x] < 128 || buf[2][x + 1] < 128) transition[x] = 255; else transition[x] = 0; } else transition[x] = 0; } if (buf[1][x] >= 128) { if (buf[0][x - 1] < 128 || buf[0][x] < 128 || buf[1][x - 1] < 128 || buf[2][x - 1] < 128 || buf[2][x] < 128) transition[x] = 255; else transition[x] = 0; } else transition[x] = 0; } KUndo2MagicString KisErodeSelectionFilter::name() { return kundo2_i18n("Erode Selection"); } QRect KisErodeSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) { Q_UNUSED(defaultBounds); const qint32 radius = 1; return rect.adjusted(-radius, -radius, radius, radius); } void KisErodeSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) { // Erode (radius 1 pixel) a mask (1bpp) quint8* buf[3]; qint32 width = rect.width(); qint32 height = rect.height(); quint8* out = new quint8[width]; for (qint32 i = 0; i < 3; i++) buf[i] = new quint8[width + 2]; // load top of image pixelSelection->readBytes(buf[0] + 1, rect.x(), rect.y(), width, 1); buf[0][0] = buf[0][1]; buf[0][width + 1] = buf[0][width]; memcpy(buf[1], buf[0], width + 2); for (qint32 y = 0; y < height; y++) { if (y + 1 < height) { pixelSelection->readBytes(buf[2] + 1, rect.x(), rect.y() + y + 1, width, 1); buf[2][0] = buf[2][1]; buf[2][width + 1] = buf[2][width]; } else { memcpy(buf[2], buf[1], width + 2); } for (qint32 x = 0 ; x < width; x++) { qint32 min = 255; if (buf[0][x+1] < min) min = buf[0][x+1]; if (buf[1][x] < min) min = buf[1][x]; if (buf[1][x+1] < min) min = buf[1][x+1]; if (buf[1][x+2] < min) min = buf[1][x+2]; if (buf[2][x+1] < min) min = buf[2][x+1]; out[x] = min; } pixelSelection->writeBytes(out, rect.x(), rect.y() + y, width, 1); rotatePointers(buf, 3); } for (qint32 i = 0; i < 3; i++) delete[] buf[i]; delete[] out; } KUndo2MagicString KisDilateSelectionFilter::name() { return kundo2_i18n("Dilate Selection"); } QRect KisDilateSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) { Q_UNUSED(defaultBounds); const qint32 radius = 1; return rect.adjusted(-radius, -radius, radius, radius); } void KisDilateSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) { // dilate (radius 1 pixel) a mask (1bpp) quint8* buf[3]; qint32 width = rect.width(); qint32 height = rect.height(); quint8* out = new quint8[width]; for (qint32 i = 0; i < 3; i++) buf[i] = new quint8[width + 2]; // load top of image pixelSelection->readBytes(buf[0] + 1, rect.x(), rect.y(), width, 1); buf[0][0] = buf[0][1]; buf[0][width + 1] = buf[0][width]; memcpy(buf[1], buf[0], width + 2); for (qint32 y = 0; y < height; y++) { if (y + 1 < height) { pixelSelection->readBytes(buf[2] + 1, rect.x(), rect.y() + y + 1, width, 1); buf[2][0] = buf[2][1]; buf[2][width + 1] = buf[2][width]; } else { memcpy(buf[2], buf[1], width + 2); } for (qint32 x = 0 ; x < width; x++) { qint32 max = 0; if (buf[0][x+1] > max) max = buf[0][x+1]; if (buf[1][x] > max) max = buf[1][x]; if (buf[1][x+1] > max) max = buf[1][x+1]; if (buf[1][x+2] > max) max = buf[1][x+2]; if (buf[2][x+1] > max) max = buf[2][x+1]; out[x] = max; } pixelSelection->writeBytes(out, rect.x(), rect.y() + y, width, 1); rotatePointers(buf, 3); } for (qint32 i = 0; i < 3; i++) delete[] buf[i]; delete[] out; } KisBorderSelectionFilter::KisBorderSelectionFilter(qint32 xRadius, qint32 yRadius) : m_xRadius(xRadius), m_yRadius(yRadius) { } KUndo2MagicString KisBorderSelectionFilter::name() { return kundo2_i18n("Border Selection"); } QRect KisBorderSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) { Q_UNUSED(defaultBounds); return rect.adjusted(-m_xRadius, -m_yRadius, m_xRadius, m_yRadius); } void KisBorderSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) { if (m_xRadius <= 0 || m_yRadius <= 0) return; quint8 *buf[3]; quint8 **density; quint8 **transition; if (m_xRadius == 1 && m_yRadius == 1) { // optimize this case specifically quint8* source[3]; for (qint32 i = 0; i < 3; i++) source[i] = new quint8[rect.width()]; quint8* transition = new quint8[rect.width()]; pixelSelection->readBytes(source[0], rect.x(), rect.y(), rect.width(), 1); memcpy(source[1], source[0], rect.width()); if (rect.height() > 1) pixelSelection->readBytes(source[2], rect.x(), rect.y() + 1, rect.width(), 1); else memcpy(source[2], source[1], rect.width()); computeTransition(transition, source, rect.width()); pixelSelection->writeBytes(transition, rect.x(), rect.y(), rect.width(), 1); for (qint32 y = 1; y < rect.height(); y++) { rotatePointers(source, 3); if (y + 1 < rect.height()) pixelSelection->readBytes(source[2], rect.x(), rect.y() + y + 1, rect.width(), 1); else memcpy(source[2], source[1], rect.width()); computeTransition(transition, source, rect.width()); pixelSelection->writeBytes(transition, rect.x(), rect.y() + y, rect.width(), 1); } for (qint32 i = 0; i < 3; i++) delete[] source[i]; delete[] transition; return; } qint32* max = new qint32[rect.width() + 2 * m_xRadius]; for (qint32 i = 0; i < (rect.width() + 2 * m_xRadius); i++) max[i] = m_yRadius + 2; max += m_xRadius; for (qint32 i = 0; i < 3; i++) buf[i] = new quint8[rect.width()]; transition = new quint8*[m_yRadius + 1]; for (qint32 i = 0; i < m_yRadius + 1; i++) { transition[i] = new quint8[rect.width() + 2 * m_xRadius]; memset(transition[i], 0, rect.width() + 2 * m_xRadius); transition[i] += m_xRadius; } quint8* out = new quint8[rect.width()]; density = new quint8*[2 * m_xRadius + 1]; density += m_xRadius; for (qint32 x = 0; x < (m_xRadius + 1); x++) { // allocate density[][] density[ x] = new quint8[2 * m_yRadius + 1]; density[ x] += m_yRadius; density[-x] = density[x]; } for (qint32 x = 0; x < (m_xRadius + 1); x++) { // compute density[][] double tmpx, tmpy, dist; quint8 a; tmpx = x > 0.0 ? x - 0.5 : 0.0; for (qint32 y = 0; y < (m_yRadius + 1); y++) { tmpy = y > 0.0 ? y - 0.5 : 0.0; dist = ((tmpy * tmpy) / (m_yRadius * m_yRadius) + (tmpx * tmpx) / (m_xRadius * m_xRadius)); if (dist < 1.0) a = (quint8)(255 * (1.0 - sqrt(dist))); else a = 0; density[ x][ y] = a; density[ x][-y] = a; density[-x][ y] = a; density[-x][-y] = a; } } pixelSelection->readBytes(buf[0], rect.x(), rect.y(), rect.width(), 1); memcpy(buf[1], buf[0], rect.width()); if (rect.height() > 1) pixelSelection->readBytes(buf[2], rect.x(), rect.y() + 1, rect.width(), 1); else memcpy(buf[2], buf[1], rect.width()); computeTransition(transition[1], buf, rect.width()); for (qint32 y = 1; y < m_yRadius && y + 1 < rect.height(); y++) { // set up top of image rotatePointers(buf, 3); pixelSelection->readBytes(buf[2], rect.x(), rect.y() + y + 1, rect.width(), 1); computeTransition(transition[y + 1], buf, rect.width()); } for (qint32 x = 0; x < rect.width(); x++) { // set up max[] for top of image max[x] = -(m_yRadius + 7); for (qint32 j = 1; j < m_yRadius + 1; j++) if (transition[j][x]) { max[x] = j; break; } } for (qint32 y = 0; y < rect.height(); y++) { // main calculation loop rotatePointers(buf, 3); rotatePointers(transition, m_yRadius + 1); if (y < rect.height() - (m_yRadius + 1)) { pixelSelection->readBytes(buf[2], rect.x(), rect.y() + y + m_yRadius + 1, rect.width(), 1); computeTransition(transition[m_yRadius], buf, rect.width()); } else memcpy(transition[m_yRadius], transition[m_yRadius - 1], rect.width()); for (qint32 x = 0; x < rect.width(); x++) { // update max array if (max[x] < 1) { if (max[x] <= -m_yRadius) { if (transition[m_yRadius][x]) max[x] = m_yRadius; else max[x]--; } else if (transition[-max[x]][x]) max[x] = -max[x]; else if (transition[-max[x] + 1][x]) max[x] = -max[x] + 1; else max[x]--; } else max[x]--; if (max[x] < -m_yRadius - 1) max[x] = -m_yRadius - 1; } quint8 last_max = max[0][density[-1]]; qint32 last_index = 1; for (qint32 x = 0 ; x < rect.width(); x++) { // render scan line last_index--; if (last_index >= 0) { last_max = 0; for (qint32 i = m_xRadius; i >= 0; i--) if (max[x + i] <= m_yRadius && max[x + i] >= -m_yRadius && density[i][max[x+i]] > last_max) { last_max = density[i][max[x + i]]; last_index = i; } out[x] = last_max; } else { last_max = 0; for (qint32 i = m_xRadius; i >= -m_xRadius; i--) if (max[x + i] <= m_yRadius && max[x + i] >= -m_yRadius && density[i][max[x + i]] > last_max) { last_max = density[i][max[x + i]]; last_index = i; } out[x] = last_max; } if (last_max == 0) { qint32 i; for (i = x + 1; i < rect.width(); i++) { if (max[i] >= -m_yRadius) break; } if (i - x > m_xRadius) { for (; x < i - m_xRadius; x++) out[x] = 0; x--; } last_index = m_xRadius; } } pixelSelection->writeBytes(out, rect.x(), rect.y() + y, rect.width(), 1); } delete [] out; for (qint32 i = 0; i < 3; i++) delete[] buf[i]; max -= m_xRadius; delete[] max; for (qint32 i = 0; i < m_yRadius + 1; i++) { transition[i] -= m_xRadius; delete transition[i]; } delete[] transition; for (qint32 i = 0; i < m_xRadius + 1 ; i++) { density[i] -= m_yRadius; delete density[i]; } density -= m_xRadius; delete[] density; } KisFeatherSelectionFilter::KisFeatherSelectionFilter(qint32 radius) : m_radius(radius) { } KUndo2MagicString KisFeatherSelectionFilter::name() { return kundo2_i18n("Feather Selection"); } QRect KisFeatherSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) { Q_UNUSED(defaultBounds); return rect.adjusted(-m_radius, -m_radius, m_radius, m_radius); } void KisFeatherSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) { // compute horizontal kernel const uint kernelSize = m_radius * 2 + 1; Eigen::Matrix gaussianMatrix(1, kernelSize); const qreal multiplicand = 1.0 / (2.0 * M_PI * m_radius * m_radius); const qreal exponentMultiplicand = 1.0 / (2.0 * m_radius * m_radius); for (uint x = 0; x < kernelSize; x++) { uint xDistance = qAbs((int)m_radius - (int)x); gaussianMatrix(0, x) = multiplicand * exp( -(qreal)((xDistance * xDistance) + (m_radius * m_radius)) * exponentMultiplicand ); } KisConvolutionKernelSP kernelHoriz = KisConvolutionKernel::fromMatrix(gaussianMatrix, 0, gaussianMatrix.sum()); KisConvolutionKernelSP kernelVertical = KisConvolutionKernel::fromMatrix(gaussianMatrix.transpose(), 0, gaussianMatrix.sum()); KisPaintDeviceSP interm = new KisPaintDevice(pixelSelection->colorSpace()); interm->prepareClone(pixelSelection); KisConvolutionPainter horizPainter(interm); horizPainter.setChannelFlags(interm->colorSpace()->channelFlags(false, true)); horizPainter.applyMatrix(kernelHoriz, pixelSelection, rect.topLeft(), rect.topLeft(), rect.size(), BORDER_REPEAT); horizPainter.end(); KisConvolutionPainter verticalPainter(pixelSelection); verticalPainter.setChannelFlags(pixelSelection->colorSpace()->channelFlags(false, true)); verticalPainter.applyMatrix(kernelVertical, interm, rect.topLeft(), rect.topLeft(), rect.size(), BORDER_REPEAT); verticalPainter.end(); } KisGrowSelectionFilter::KisGrowSelectionFilter(qint32 xRadius, qint32 yRadius) : m_xRadius(xRadius), m_yRadius(yRadius) { } KUndo2MagicString KisGrowSelectionFilter::name() { return kundo2_i18n("Grow Selection"); } QRect KisGrowSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) { Q_UNUSED(defaultBounds); return rect.adjusted(-m_xRadius, -m_yRadius, m_xRadius, m_yRadius); } void KisGrowSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) { if (m_xRadius <= 0 || m_yRadius <= 0) return; /** * Much code resembles Shrink filter, so please fix bugs * in both filters */ quint8 **buf; // caches the region's pixel data quint8 **max; // caches the largest values for each column max = new quint8* [rect.width() + 2 * m_xRadius]; buf = new quint8* [m_yRadius + 1]; for (qint32 i = 0; i < m_yRadius + 1; i++) { buf[i] = new quint8[rect.width()]; } quint8* buffer = new quint8[(rect.width() + 2 * m_xRadius) *(m_yRadius + 1)]; for (qint32 i = 0; i < rect.width() + 2 * m_xRadius; i++) { if (i < m_xRadius) max[i] = buffer; else if (i < rect.width() + m_xRadius) max[i] = &buffer[(m_yRadius + 1) * (i - m_xRadius)]; else max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius - 1)]; for (qint32 j = 0; j < m_xRadius + 1; j++) max[i][j] = 0; } /* offset the max pointer by m_xRadius so the range of the array is [-m_xRadius] to [region->w + m_xRadius] */ max += m_xRadius; quint8* out = new quint8[ rect.width()]; // holds the new scan line we are computing qint32* circ = new qint32[ 2 * m_xRadius + 1 ]; // holds the y coords of the filter's mask computeBorder(circ, m_xRadius, m_yRadius); /* offset the circ pointer by m_xRadius so the range of the array is [-m_xRadius] to [m_xRadius] */ circ += m_xRadius; memset(buf[0], 0, rect.width()); for (qint32 i = 0; i < m_yRadius && i < rect.height(); i++) { // load top of image pixelSelection->readBytes(buf[i + 1], rect.x(), rect.y() + i, rect.width(), 1); } for (qint32 x = 0; x < rect.width() ; x++) { // set up max for top of image max[x][0] = 0; // buf[0][x] is always 0 max[x][1] = buf[1][x]; // MAX (buf[1][x], max[x][0]) always = buf[1][x] for (qint32 j = 2; j < m_yRadius + 1; j++) { max[x][j] = MAX(buf[j][x], max[x][j-1]); } } for (qint32 y = 0; y < rect.height(); y++) { rotatePointers(buf, m_yRadius + 1); if (y < rect.height() - (m_yRadius)) pixelSelection->readBytes(buf[m_yRadius], rect.x(), rect.y() + y + m_yRadius, rect.width(), 1); else memset(buf[m_yRadius], 0, rect.width()); for (qint32 x = 0; x < rect.width(); x++) { /* update max array */ for (qint32 i = m_yRadius; i > 0; i--) { max[x][i] = MAX(MAX(max[x][i - 1], buf[i - 1][x]), buf[i][x]); } max[x][0] = buf[0][x]; } qint32 last_max = max[0][circ[-1]]; qint32 last_index = 1; for (qint32 x = 0; x < rect.width(); x++) { /* render scan line */ last_index--; if (last_index >= 0) { if (last_max == 255) out[x] = 255; else { last_max = 0; for (qint32 i = m_xRadius; i >= 0; i--) if (last_max < max[x + i][circ[i]]) { last_max = max[x + i][circ[i]]; last_index = i; } out[x] = last_max; } } else { last_index = m_xRadius; last_max = max[x + m_xRadius][circ[m_xRadius]]; for (qint32 i = m_xRadius - 1; i >= -m_xRadius; i--) if (last_max < max[x + i][circ[i]]) { last_max = max[x + i][circ[i]]; last_index = i; } out[x] = last_max; } } pixelSelection->writeBytes(out, rect.x(), rect.y() + y, rect.width(), 1); } /* undo the offsets to the pointers so we can free the malloced memory */ circ -= m_xRadius; max -= m_xRadius; delete[] circ; delete[] buffer; delete[] max; for (qint32 i = 0; i < m_yRadius + 1; i++) delete[] buf[i]; delete[] buf; delete[] out; } KisShrinkSelectionFilter::KisShrinkSelectionFilter(qint32 xRadius, qint32 yRadius, bool edgeLock) : m_xRadius(xRadius), m_yRadius(yRadius), m_edgeLock(edgeLock) { } KUndo2MagicString KisShrinkSelectionFilter::name() { return kundo2_i18n("Shrink Selection"); } QRect KisShrinkSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) { Q_UNUSED(defaultBounds); return rect.adjusted(-m_xRadius, -m_yRadius, m_xRadius, m_yRadius); } void KisShrinkSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) { if (m_xRadius <= 0 || m_yRadius <= 0) return; /* pretty much the same as fatten_region only different blame all bugs in this function on jaycox@gimp.org */ /* If edge_lock is true we assume that pixels outside the region we are passed are identical to the edge pixels. If edge_lock is false, we assume that pixels outside the region are 0 */ quint8 **buf; // caches the region's pixels quint8 **max; // caches the smallest values for each column qint32 last_max, last_index; max = new quint8* [rect.width() + 2 * m_xRadius]; buf = new quint8* [m_yRadius + 1]; for (qint32 i = 0; i < m_yRadius + 1; i++) { buf[i] = new quint8[rect.width()]; } qint32 buffer_size = (rect.width() + 2 * m_xRadius + 1) * (m_yRadius + 1); quint8* buffer = new quint8[buffer_size]; if (m_edgeLock) memset(buffer, 255, buffer_size); else memset(buffer, 0, buffer_size); for (qint32 i = 0; i < rect.width() + 2 * m_xRadius; i++) { if (i < m_xRadius) if (m_edgeLock) max[i] = buffer; else max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius)]; else if (i < rect.width() + m_xRadius) max[i] = &buffer[(m_yRadius + 1) * (i - m_xRadius)]; else if (m_edgeLock) max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius - 1)]; else max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius)]; } if (!m_edgeLock) for (qint32 j = 0 ; j < m_xRadius + 1; j++) max[0][j] = 0; // offset the max pointer by m_xRadius so the range of the array is [-m_xRadius] to [region->w + m_xRadius] max += m_xRadius; quint8* out = new quint8[rect.width()]; // holds the new scan line we are computing qint32* circ = new qint32[2 * m_xRadius + 1]; // holds the y coords of the filter's mask computeBorder(circ, m_xRadius, m_yRadius); // offset the circ pointer by m_xRadius so the range of the array is [-m_xRadius] to [m_xRadius] circ += m_xRadius; for (qint32 i = 0; i < m_yRadius && i < rect.height(); i++) // load top of image pixelSelection->readBytes(buf[i + 1], rect.x(), rect.y() + i, rect.width(), 1); if (m_edgeLock) memcpy(buf[0], buf[1], rect.width()); else memset(buf[0], 0, rect.width()); for (qint32 x = 0; x < rect.width(); x++) { // set up max for top of image max[x][0] = buf[0][x]; for (qint32 j = 1; j < m_yRadius + 1; j++) max[x][j] = MIN(buf[j][x], max[x][j-1]); } for (qint32 y = 0; y < rect.height(); y++) { rotatePointers(buf, m_yRadius + 1); if (y < rect.height() - m_yRadius) pixelSelection->readBytes(buf[m_yRadius], rect.x(), rect.y() + y + m_yRadius, rect.width(), 1); else if (m_edgeLock) memcpy(buf[m_yRadius], buf[m_yRadius - 1], rect.width()); else memset(buf[m_yRadius], 0, rect.width()); for (qint32 x = 0 ; x < rect.width(); x++) { // update max array for (qint32 i = m_yRadius; i > 0; i--) { max[x][i] = MIN(MIN(max[x][i - 1], buf[i - 1][x]), buf[i][x]); } max[x][0] = buf[0][x]; } last_max = max[0][circ[-1]]; last_index = 0; for (qint32 x = 0 ; x < rect.width(); x++) { // render scan line last_index--; if (last_index >= 0) { if (last_max == 0) out[x] = 0; else { last_max = 255; for (qint32 i = m_xRadius; i >= 0; i--) if (last_max > max[x + i][circ[i]]) { last_max = max[x + i][circ[i]]; last_index = i; } out[x] = last_max; } } else { last_index = m_xRadius; last_max = max[x + m_xRadius][circ[m_xRadius]]; for (qint32 i = m_xRadius - 1; i >= -m_xRadius; i--) if (last_max > max[x + i][circ[i]]) { last_max = max[x + i][circ[i]]; last_index = i; } out[x] = last_max; } } pixelSelection->writeBytes(out, rect.x(), rect.y() + y, rect.width(), 1); } // undo the offsets to the pointers so we can free the malloced memory circ -= m_xRadius; max -= m_xRadius; delete[] circ; delete[] buffer; delete[] max; for (qint32 i = 0; i < m_yRadius + 1; i++) delete[] buf[i]; delete[] buf; delete[] out; } KUndo2MagicString KisSmoothSelectionFilter::name() { return kundo2_i18n("Smooth Selection"); } QRect KisSmoothSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds) { Q_UNUSED(defaultBounds); const qint32 radius = 1; return rect.adjusted(-radius, -radius, radius, radius); } void KisSmoothSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) { // Simple convolution filter to smooth a mask (1bpp) quint8 *buf[3]; qint32 width = rect.width(); qint32 height = rect.height(); quint8* out = new quint8[width]; for (qint32 i = 0; i < 3; i++) buf[i] = new quint8[width + 2]; // load top of image pixelSelection->readBytes(buf[0] + 1, rect.x(), rect.y(), width, 1); buf[0][0] = buf[0][1]; buf[0][width + 1] = buf[0][width]; memcpy(buf[1], buf[0], width + 2); for (qint32 y = 0; y < height; y++) { if (y + 1 < height) { pixelSelection->readBytes(buf[2] + 1, rect.x(), rect.y() + y + 1, width, 1); buf[2][0] = buf[2][1]; buf[2][width + 1] = buf[2][width]; } else { memcpy(buf[2], buf[1], width + 2); } for (qint32 x = 0 ; x < width; x++) { qint32 value = (buf[0][x] + buf[0][x+1] + buf[0][x+2] + buf[1][x] + buf[2][x+1] + buf[1][x+2] + buf[2][x] + buf[1][x+1] + buf[2][x+2]); out[x] = value / 9; } pixelSelection->writeBytes(out, rect.x(), rect.y() + y, width, 1); rotatePointers(buf, 3); } for (qint32 i = 0; i < 3; i++) delete[] buf[i]; delete[] out; } KUndo2MagicString KisInvertSelectionFilter::name() { return kundo2_i18n("Invert Selection"); } QRect KisInvertSelectionFilter::changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds) { Q_UNUSED(rect); return defaultBounds->bounds(); } void KisInvertSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect) { Q_UNUSED(rect); pixelSelection->invert(); } diff --git a/libs/image/kis_stroke.cpp b/libs/image/kis_stroke.cpp index 3563333d6a..47fbdbc4a8 100644 --- a/libs/image/kis_stroke.cpp +++ b/libs/image/kis_stroke.cpp @@ -1,336 +1,336 @@ /* * 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_stroke.h" #include "kis_stroke_strategy.h" KisStroke::KisStroke(KisStrokeStrategy *strokeStrategy, Type type, int levelOfDetail) : m_strokeStrategy(strokeStrategy), m_strokeInitialized(false), m_strokeEnded(false), m_strokeSuspended(false), m_isCancelled(false), m_worksOnLevelOfDetail(levelOfDetail), m_type(type) { m_initStrategy.reset(m_strokeStrategy->createInitStrategy()); m_dabStrategy.reset(m_strokeStrategy->createDabStrategy()); m_cancelStrategy.reset(m_strokeStrategy->createCancelStrategy()); m_finishStrategy.reset(m_strokeStrategy->createFinishStrategy()); m_suspendStrategy.reset(m_strokeStrategy->createSuspendStrategy()); m_resumeStrategy.reset(m_strokeStrategy->createResumeStrategy()); m_strokeStrategy->notifyUserStartedStroke(); if(!m_initStrategy) { m_strokeInitialized = true; } else { enqueue(m_initStrategy.data(), m_strokeStrategy->createInitData()); } } KisStroke::~KisStroke() { Q_ASSERT(m_strokeEnded); Q_ASSERT(m_jobsQueue.isEmpty()); } bool KisStroke::supportsSuspension() { return !m_strokeInitialized || (m_suspendStrategy && m_resumeStrategy); } void KisStroke::suspendStroke(KisStrokeSP recipient) { if (!m_strokeInitialized || m_strokeSuspended || (m_strokeEnded && !hasJobs())) { return; } KIS_ASSERT_RECOVER_NOOP(m_suspendStrategy && m_resumeStrategy); prepend(m_resumeStrategy.data(), m_strokeStrategy->createResumeData(), worksOnLevelOfDetail(), false); recipient->prepend(m_suspendStrategy.data(), m_strokeStrategy->createSuspendData(), worksOnLevelOfDetail(), false); m_strokeSuspended = true; } void KisStroke::addJob(KisStrokeJobData *data) { - Q_ASSERT(!m_strokeEnded || m_isCancelled); + KIS_SAFE_ASSERT_RECOVER_NOOP(!m_strokeEnded); enqueue(m_dabStrategy.data(), data); } void KisStroke::addMutatedJobs(const QVector list) { // factory methods can return null, if no action is needed if (!m_dabStrategy) { qDeleteAll(list); return; } // Find first non-alien (non-suspend/non-resume) job // // Please note that this algorithm will stop working at the day we start // adding alien jobs not to the beginning of the stroke, but to other places. // Right now both suspend and resume jobs are added to the beginning of // the stroke. auto it = std::find_if(m_jobsQueue.begin(), m_jobsQueue.end(), [] (KisStrokeJob *job) { return job->isOwnJob(); }); Q_FOREACH (KisStrokeJobData *data, list) { it = m_jobsQueue.insert(it, new KisStrokeJob(m_dabStrategy.data(), data, worksOnLevelOfDetail(), true)); ++it; } } KisStrokeJob* KisStroke::popOneJob() { KisStrokeJob *job = dequeue(); if(job) { m_strokeInitialized = true; m_strokeSuspended = false; } return job; } KUndo2MagicString KisStroke::name() const { return m_strokeStrategy->name(); } bool KisStroke::hasJobs() const { return !m_jobsQueue.isEmpty(); } qint32 KisStroke::numJobs() const { return m_jobsQueue.size(); } void KisStroke::endStroke() { KIS_SAFE_ASSERT_RECOVER_RETURN(!m_strokeEnded); m_strokeEnded = true; enqueue(m_finishStrategy.data(), m_strokeStrategy->createFinishData()); m_strokeStrategy->notifyUserEndedStroke(); } /** * About cancelling the stroke * There may be four different states of the stroke, when cancel * is requested: * 1) Not initialized, has jobs -- just clear the queue * 2) Initialized, has jobs, not finished -- clear the queue, * enqueue the cancel job * 5) Initialized, no jobs, not finished -- enqueue the cancel job * 3) Initialized, has jobs, finished -- clear the queue, enqueue * the cancel job * 4) Initialized, no jobs, finished -- it's too late to cancel * anything * 6) Initialized, has jobs, cancelled -- cancelling twice is a permitted * operation, though it does nothing */ void KisStroke::cancelStroke() { // case 6 if (m_isCancelled) return; const bool effectivelyInitialized = m_strokeInitialized || m_strokeStrategy->needsExplicitCancel(); if(!effectivelyInitialized) { /** * Lod0 stroke cannot be suspended and !initialized at the * same time, because the suspend job is created iff the * stroke has already done some meaningful work. * * At the same time, LodN stroke can be prepended with a * 'suspend' job even when it has not been started yet. That * is obvious: we should suspend the other stroke before doing * anything else. */ KIS_ASSERT_RECOVER_NOOP(type() == LODN || sanityCheckAllJobsAreCancellable()); clearQueueOnCancel(); } else if(effectivelyInitialized && (!m_jobsQueue.isEmpty() || !m_strokeEnded)) { clearQueueOnCancel(); enqueue(m_cancelStrategy.data(), m_strokeStrategy->createCancelData()); } // else { // too late ... // } m_isCancelled = true; m_strokeEnded = true; } bool KisStroke::canCancel() const { return m_isCancelled || !m_strokeInitialized || !m_jobsQueue.isEmpty() || !m_strokeEnded; } bool KisStroke::sanityCheckAllJobsAreCancellable() const { Q_FOREACH (KisStrokeJob *item, m_jobsQueue) { if (!item->isCancellable()) { return false; } } return true; } void KisStroke::clearQueueOnCancel() { QQueue::iterator it = m_jobsQueue.begin(); while (it != m_jobsQueue.end()) { if ((*it)->isCancellable()) { delete (*it); it = m_jobsQueue.erase(it); } else { ++it; } } } bool KisStroke::isInitialized() const { return m_strokeInitialized; } bool KisStroke::isEnded() const { return m_strokeEnded; } bool KisStroke::isCancelled() const { return m_isCancelled; } bool KisStroke::isExclusive() const { return m_strokeStrategy->isExclusive(); } bool KisStroke::supportsWrapAroundMode() const { return m_strokeStrategy->supportsWrapAroundMode(); } int KisStroke::worksOnLevelOfDetail() const { return m_worksOnLevelOfDetail; } bool KisStroke::canForgetAboutMe() const { return m_strokeStrategy->canForgetAboutMe(); } qreal KisStroke::balancingRatioOverride() const { return m_strokeStrategy->balancingRatioOverride(); } KisStrokeJobData::Sequentiality KisStroke::nextJobSequentiality() const { return !m_jobsQueue.isEmpty() ? m_jobsQueue.head()->sequentiality() : KisStrokeJobData::SEQUENTIAL; } void KisStroke::enqueue(KisStrokeJobStrategy *strategy, KisStrokeJobData *data) { // factory methods can return null, if no action is needed if(!strategy) { delete data; return; } m_jobsQueue.enqueue(new KisStrokeJob(strategy, data, worksOnLevelOfDetail(), true)); } void KisStroke::prepend(KisStrokeJobStrategy *strategy, KisStrokeJobData *data, int levelOfDetail, bool isOwnJob) { // factory methods can return null, if no action is needed if(!strategy) { delete data; return; } // LOG_MERGE_FIXME: Q_UNUSED(levelOfDetail); m_jobsQueue.prepend(new KisStrokeJob(strategy, data, worksOnLevelOfDetail(), isOwnJob)); } KisStrokeJob* KisStroke::dequeue() { return !m_jobsQueue.isEmpty() ? m_jobsQueue.dequeue() : 0; } void KisStroke::setLodBuddy(KisStrokeSP buddy) { m_lodBuddy = buddy; } KisStrokeSP KisStroke::lodBuddy() const { return m_lodBuddy; } KisStroke::Type KisStroke::type() const { if (m_type == LOD0) { KIS_ASSERT_RECOVER_NOOP(m_lodBuddy && "LOD0 strokes must always have a buddy"); } else if (m_type == LODN) { KIS_ASSERT_RECOVER_NOOP(m_worksOnLevelOfDetail > 0 && "LODN strokes must work on LOD > 0!"); } else if (m_type == LEGACY) { KIS_ASSERT_RECOVER_NOOP(m_worksOnLevelOfDetail == 0 && "LEGACY strokes must work on LOD == 0!"); } return m_type; } diff --git a/libs/image/kis_stroke_strategy.cpp b/libs/image/kis_stroke_strategy.cpp index cec29bd135..aa75ac8161 100644 --- a/libs/image/kis_stroke_strategy.cpp +++ b/libs/image/kis_stroke_strategy.cpp @@ -1,226 +1,227 @@ /* * 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_stroke_strategy.h" #include #include "kis_stroke_job_strategy.h" #include "KisStrokesQueueMutatedJobInterface.h" KisStrokeStrategy::KisStrokeStrategy(const QLatin1String &id, const KUndo2MagicString &name) : m_exclusive(false), m_supportsWrapAroundMode(false), m_clearsRedoOnStart(true), m_requestsOtherStrokesToEnd(true), m_canForgetAboutMe(false), m_needsExplicitCancel(false), m_balancingRatioOverride(-1.0), m_id(id), m_name(name), m_mutatedJobsInterface(0) { } KisStrokeStrategy::KisStrokeStrategy(const KisStrokeStrategy &rhs) : m_exclusive(rhs.m_exclusive), m_supportsWrapAroundMode(rhs.m_supportsWrapAroundMode), m_clearsRedoOnStart(rhs.m_clearsRedoOnStart), m_requestsOtherStrokesToEnd(rhs.m_requestsOtherStrokesToEnd), m_canForgetAboutMe(rhs.m_canForgetAboutMe), m_needsExplicitCancel(rhs.m_needsExplicitCancel), m_balancingRatioOverride(rhs.m_balancingRatioOverride), m_id(rhs.m_id), m_name(rhs.m_name), m_mutatedJobsInterface(0) { - KIS_ASSERT_RECOVER_NOOP(!rhs.m_cancelStrokeId && !m_mutatedJobsInterface && + KIS_ASSERT_RECOVER_NOOP(!rhs.m_strokeId && !m_mutatedJobsInterface && "After the stroke has been started, no copying must happen"); } KisStrokeStrategy::~KisStrokeStrategy() { } void KisStrokeStrategy::notifyUserStartedStroke() { } void KisStrokeStrategy::notifyUserEndedStroke() { } KisStrokeJobStrategy* KisStrokeStrategy::createInitStrategy() { return 0; } KisStrokeJobStrategy* KisStrokeStrategy::createFinishStrategy() { return 0; } KisStrokeJobStrategy* KisStrokeStrategy::createCancelStrategy() { return 0; } KisStrokeJobStrategy* KisStrokeStrategy::createDabStrategy() { return 0; } KisStrokeJobStrategy* KisStrokeStrategy::createSuspendStrategy() { return 0; } KisStrokeJobStrategy* KisStrokeStrategy::createResumeStrategy() { return 0; } KisStrokeJobData* KisStrokeStrategy::createInitData() { return 0; } KisStrokeJobData* KisStrokeStrategy::createFinishData() { return 0; } KisStrokeJobData* KisStrokeStrategy::createCancelData() { return 0; } KisStrokeJobData* KisStrokeStrategy::createSuspendData() { return 0; } KisStrokeJobData* KisStrokeStrategy::createResumeData() { return 0; } KisStrokeStrategy* KisStrokeStrategy::createLodClone(int levelOfDetail) { Q_UNUSED(levelOfDetail); return 0; } bool KisStrokeStrategy::isExclusive() const { return m_exclusive; } bool KisStrokeStrategy::supportsWrapAroundMode() const { return m_supportsWrapAroundMode; } QString KisStrokeStrategy::id() const { return m_id; } KUndo2MagicString KisStrokeStrategy::name() const { return m_name; } -void KisStrokeStrategy::setMutatedJobsInterface(KisStrokesQueueMutatedJobInterface *mutatedJobsInterface) +void KisStrokeStrategy::setMutatedJobsInterface(KisStrokesQueueMutatedJobInterface *mutatedJobsInterface, KisStrokeId strokeId) { m_mutatedJobsInterface = mutatedJobsInterface; + m_strokeId = strokeId; } void KisStrokeStrategy::addMutatedJobs(const QVector list) { - KIS_SAFE_ASSERT_RECOVER(m_mutatedJobsInterface && m_cancelStrokeId) { + KIS_SAFE_ASSERT_RECOVER(m_mutatedJobsInterface && m_strokeId) { qDeleteAll(list); return; } - m_mutatedJobsInterface->addMutatedJobs(m_cancelStrokeId, list); + m_mutatedJobsInterface->addMutatedJobs(m_strokeId, list); } void KisStrokeStrategy::addMutatedJob(KisStrokeJobData *data) { addMutatedJobs({data}); } void KisStrokeStrategy::setExclusive(bool value) { m_exclusive = value; } void KisStrokeStrategy::setSupportsWrapAroundMode(bool value) { m_supportsWrapAroundMode = value; } bool KisStrokeStrategy::clearsRedoOnStart() const { return m_clearsRedoOnStart; } void KisStrokeStrategy::setClearsRedoOnStart(bool value) { m_clearsRedoOnStart = value; } bool KisStrokeStrategy::requestsOtherStrokesToEnd() const { return m_requestsOtherStrokesToEnd; } void KisStrokeStrategy::setRequestsOtherStrokesToEnd(bool value) { m_requestsOtherStrokesToEnd = value; } bool KisStrokeStrategy::canForgetAboutMe() const { return m_canForgetAboutMe; } void KisStrokeStrategy::setCanForgetAboutMe(bool value) { m_canForgetAboutMe = value; } bool KisStrokeStrategy::needsExplicitCancel() const { return m_needsExplicitCancel; } void KisStrokeStrategy::setNeedsExplicitCancel(bool value) { m_needsExplicitCancel = value; } qreal KisStrokeStrategy::balancingRatioOverride() const { return m_balancingRatioOverride; } void KisStrokeStrategy::setBalancingRatioOverride(qreal value) { m_balancingRatioOverride = value; } diff --git a/libs/image/kis_stroke_strategy.h b/libs/image/kis_stroke_strategy.h index b5dce84c68..dd21d269ed 100644 --- a/libs/image/kis_stroke_strategy.h +++ b/libs/image/kis_stroke_strategy.h @@ -1,209 +1,194 @@ /* * 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_STROKE_STRATEGY_H #define __KIS_STROKE_STRATEGY_H #include #include "kis_types.h" #include "kundo2magicstring.h" #include "kritaimage_export.h" class KisStrokeJobStrategy; class KisStrokeJobData; class KisStrokesQueueMutatedJobInterface; class KRITAIMAGE_EXPORT KisStrokeStrategy { public: KisStrokeStrategy(const QLatin1String &id, const KUndo2MagicString &name = KUndo2MagicString()); virtual ~KisStrokeStrategy(); /** * notifyUserStartedStroke() is a callback used by the strokes system to notify * when the user adds the stroke to the strokes queue. That moment corresponds * to the user calling strokesFacade->startStroke(strategy) and might happen much * earlier than the first job being executed. * * NOTE: this method will be executed in the context of the GUI thread! */ virtual void notifyUserStartedStroke(); /** * notifyUserEndedStroke() is a callback used by the strokes system to notify * when the user ends the stroke. That moment corresponds to the user calling * strokesFacade->endStroke(id) and might happen much earlier when the stroke * even started its execution. * * NOTE: this method will be executed in the context of the GUI thread! */ virtual void notifyUserEndedStroke(); virtual KisStrokeJobStrategy* createInitStrategy(); virtual KisStrokeJobStrategy* createFinishStrategy(); virtual KisStrokeJobStrategy* createCancelStrategy(); virtual KisStrokeJobStrategy* createDabStrategy(); virtual KisStrokeJobStrategy* createSuspendStrategy(); virtual KisStrokeJobStrategy* createResumeStrategy(); virtual KisStrokeJobData* createInitData(); virtual KisStrokeJobData* createFinishData(); virtual KisStrokeJobData* createCancelData(); virtual KisStrokeJobData* createSuspendData(); virtual KisStrokeJobData* createResumeData(); virtual KisStrokeStrategy* createLodClone(int levelOfDetail); bool isExclusive() const; bool supportsWrapAroundMode() const; /** * Returns true if mere start of the stroke should cancel all the * pending redo tasks. * * This method should return true in almost all circumstances * except if we are running an undo or redo stroke. */ bool clearsRedoOnStart() const; /** * Returns true if the other currently running strokes should be * politely asked to exit. The default value is 'true'. * * The only known exception right now is * KisRegenerateFrameStrokeStrategy which does not requests ending * of any actions, since it performs purely background action. */ bool requestsOtherStrokesToEnd() const; /** * Returns true if the update scheduler can cancel this stroke * when some other stroke is going to be started. This makes the * "forgettable" stroke very low priority. * * Default is 'false'. */ bool canForgetAboutMe() const; bool needsExplicitCancel() const; /** * \see setBalancingRatioOverride() for details */ qreal balancingRatioOverride() const; QString id() const; KUndo2MagicString name() const; - /** - * Set up by the strokes queue during the stroke initialization - */ - void setCancelStrokeId(KisStrokeId id) { m_cancelStrokeId = id; } - - void setMutatedJobsInterface(KisStrokesQueueMutatedJobInterface *mutatedJobsInterface); + void setMutatedJobsInterface(KisStrokesQueueMutatedJobInterface *mutatedJobsInterface, KisStrokeId strokeId); protected: // testing surrogate class friend class KisMutatableDabStrategy; - /** - * The cancel job may populate the stroke with some new jobs - * for cancelling. To achieve this it needs the stroke id. - * - * WARNING: you can't add new jobs in any places other than - * cancel job, because the stroke may be ended in any moment - * by the user and the sequence of jobs will be broken - */ - KisStrokeId cancelStrokeId() { return m_cancelStrokeId; } - /** * This function is supposed to be called by internal asynchronous * jobs. It allows adding subtasks that may be executed concurrently. * * Requirements: * * must be called *only* from within the context of the strokes * worker thread during execution of one of its jobs * * Guarantees: * * the added job is guaranteed to be executed in some time after * the currently executed job, *before* the next SEQUENTIAL or * BARRIER job * * if the currently executed job is CUNCURRENTthe mutated job *may* * start execution right after adding to the queue without waiting for * its parent to complete. Though this behavior is *not* guaranteed, * because addMutatedJob does not initiate processQueues(), because * it may lead to a deadlock. */ void addMutatedJobs(const QVector list); /** * Convenience override for addMutatedJobs() */ void addMutatedJob(KisStrokeJobData *data); // you are not supposed to change these parameters // after the KisStroke object has been created void setExclusive(bool value); void setSupportsWrapAroundMode(bool value); void setClearsRedoOnStart(bool value); void setRequestsOtherStrokesToEnd(bool value); void setCanForgetAboutMe(bool value); void setNeedsExplicitCancel(bool value); /** * Set override for the desired scheduler balancing ratio: * * ratio = stroke jobs / update jobs * * If \p value < 1.0, then the priority is given to updates, if * the value is higher than 1.0, then the priority is given * to stroke jobs. * * Special value -1.0, suggests the scheduler to use the default value * set by the user's config file (which is 100.0 by default). */ void setBalancingRatioOverride(qreal value); protected: /** * Protected c-tor, used for cloning of hi-level strategies */ KisStrokeStrategy(const KisStrokeStrategy &rhs); private: bool m_exclusive; bool m_supportsWrapAroundMode; bool m_clearsRedoOnStart; bool m_requestsOtherStrokesToEnd; bool m_canForgetAboutMe; bool m_needsExplicitCancel; qreal m_balancingRatioOverride; QLatin1String m_id; KUndo2MagicString m_name; - KisStrokeId m_cancelStrokeId; + KisStrokeId m_strokeId; KisStrokesQueueMutatedJobInterface *m_mutatedJobsInterface; }; #endif /* __KIS_STROKE_STRATEGY_H */ diff --git a/libs/image/kis_stroke_strategy_undo_command_based.cpp b/libs/image/kis_stroke_strategy_undo_command_based.cpp index 32ef12c95c..cae7a750dd 100644 --- a/libs/image/kis_stroke_strategy_undo_command_based.cpp +++ b/libs/image/kis_stroke_strategy_undo_command_based.cpp @@ -1,189 +1,192 @@ /* * 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_stroke_strategy_undo_command_based.h" #include #include "kis_image_interfaces.h" #include "kis_post_execution_undo_adapter.h" #include "commands_new/kis_saved_commands.h" KisStrokeStrategyUndoCommandBased:: KisStrokeStrategyUndoCommandBased(const KUndo2MagicString &name, bool undo, KisStrokeUndoFacade *undoFacade, KUndo2CommandSP initCommand, KUndo2CommandSP finishCommand) : KisRunnableBasedStrokeStrategy(QLatin1String("STROKE_UNDO_COMMAND_BASED"), name), m_undo(undo), m_initCommand(initCommand), m_finishCommand(finishCommand), m_undoFacade(undoFacade), m_macroId(-1), m_macroCommand(0) { enableJob(KisSimpleStrokeStrategy::JOB_INIT); enableJob(KisSimpleStrokeStrategy::JOB_FINISH); enableJob(KisSimpleStrokeStrategy::JOB_CANCEL); enableJob(KisSimpleStrokeStrategy::JOB_DOSTROKE); } KisStrokeStrategyUndoCommandBased:: KisStrokeStrategyUndoCommandBased(const KisStrokeStrategyUndoCommandBased &rhs) : KisRunnableBasedStrokeStrategy(rhs), m_undo(false), m_initCommand(rhs.m_initCommand), m_finishCommand(rhs.m_finishCommand), m_undoFacade(rhs.m_undoFacade), m_macroCommand(0) { KIS_ASSERT_RECOVER_NOOP(!rhs.m_macroCommand && !rhs.m_undo && "After the stroke has been started, no copying must happen"); } void KisStrokeStrategyUndoCommandBased::setUsedWhileUndoRedo(bool value) { setClearsRedoOnStart(!value); } void KisStrokeStrategyUndoCommandBased::executeCommand(KUndo2CommandSP command, bool undo) { if(!command) return; if (MutatedCommandInterface *mutatedCommand = dynamic_cast(command.data())) { mutatedCommand->setRunnableJobsInterface(this->runnableJobsInterface()); } if(undo) { command->undo(); } else { command->redo(); } } void KisStrokeStrategyUndoCommandBased::initStrokeCallback() { if(m_undoFacade) { m_macroCommand = m_undoFacade->postExecutionUndoAdapter()->createMacro(name()); } executeCommand(m_initCommand, m_undo); notifyCommandDone(m_initCommand, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } void KisStrokeStrategyUndoCommandBased::finishStrokeCallback() { executeCommand(m_finishCommand, m_undo); notifyCommandDone(m_finishCommand, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); QMutexLocker locker(&m_mutex); if(m_macroCommand) { Q_ASSERT(m_undoFacade); postProcessToplevelCommand(m_macroCommand); m_undoFacade->postExecutionUndoAdapter()->addMacro(m_macroCommand); m_macroCommand = 0; } } void KisStrokeStrategyUndoCommandBased::cancelStrokeCallback() { QMutexLocker locker(&m_mutex); if(m_macroCommand) { - m_macroCommand->performCancel(cancelStrokeId(), m_undo); + QVector jobs; + m_macroCommand->getCommandExecutionJobs(&jobs, !m_undo); + addMutatedJobs(jobs); + delete m_macroCommand; m_macroCommand = 0; } } void KisStrokeStrategyUndoCommandBased::doStrokeCallback(KisStrokeJobData *data) { Data *d = dynamic_cast(data); if (d) { executeCommand(d->command, d->undo); if (d->shouldGoToHistory) { notifyCommandDone(d->command, d->sequentiality(), d->exclusivity()); } } else { KisRunnableBasedStrokeStrategy::doStrokeCallback(data); } } void KisStrokeStrategyUndoCommandBased::runAndSaveCommand(KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity) { if (!command) return; executeCommand(command, false); notifyCommandDone(command, sequentiality, exclusivity); } void KisStrokeStrategyUndoCommandBased::notifyCommandDone(KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity) { if(!command) return; QMutexLocker locker(&m_mutex); if(m_macroCommand) { m_macroCommand->addCommand(command, sequentiality, exclusivity); } } void KisStrokeStrategyUndoCommandBased::setCommandExtraData(KUndo2CommandExtraData *data) { if (m_undoFacade && m_macroCommand) { warnKrita << "WARNING: KisStrokeStrategyUndoCommandBased::setCommandExtraData():" << "the extra data is set while the stroke has already been started!" << "The result is undefined, continued actions may not work!"; } m_commandExtraData.reset(data); } void KisStrokeStrategyUndoCommandBased::setMacroId(int value) { m_macroId = value; } void KisStrokeStrategyUndoCommandBased::postProcessToplevelCommand(KUndo2Command *command) { if (m_commandExtraData) { command->setExtraData(m_commandExtraData.take()); } KisSavedMacroCommand *savedCommand = dynamic_cast(command); if (savedCommand) { savedCommand->setMacroId(m_macroId); } } KisStrokeUndoFacade* KisStrokeStrategyUndoCommandBased::undoFacade() const { return m_undoFacade; } diff --git a/libs/image/kis_strokes_queue.cpp b/libs/image/kis_strokes_queue.cpp index f7e08484ff..51ec106387 100644 --- a/libs/image/kis_strokes_queue.cpp +++ b/libs/image/kis_strokes_queue.cpp @@ -1,814 +1,810 @@ /* * 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_strokes_queue.h" #include #include #include #include "kis_stroke.h" #include "kis_updater_context.h" #include "kis_stroke_job_strategy.h" #include "kis_stroke_strategy.h" #include "kis_undo_stores.h" #include "kis_post_execution_undo_adapter.h" typedef QQueue StrokesQueue; typedef QQueue::iterator StrokesQueueIterator; #include "kis_image_interfaces.h" class KisStrokesQueue::LodNUndoStrokesFacade : public KisStrokesFacade { public: LodNUndoStrokesFacade(KisStrokesQueue *_q) : q(_q) {} KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override { return q->startLodNUndoStroke(strokeStrategy); } void addJob(KisStrokeId id, KisStrokeJobData *data) override { KisStrokeSP stroke = id.toStrongRef(); KIS_SAFE_ASSERT_RECOVER_NOOP(stroke); KIS_SAFE_ASSERT_RECOVER_NOOP(!stroke->lodBuddy()); KIS_SAFE_ASSERT_RECOVER_NOOP(stroke->type() == KisStroke::LODN); q->addJob(id, data); } void endStroke(KisStrokeId id) override { KisStrokeSP stroke = id.toStrongRef(); KIS_SAFE_ASSERT_RECOVER_NOOP(stroke); KIS_SAFE_ASSERT_RECOVER_NOOP(!stroke->lodBuddy()); KIS_SAFE_ASSERT_RECOVER_NOOP(stroke->type() == KisStroke::LODN); q->endStroke(id); } bool cancelStroke(KisStrokeId id) override { Q_UNUSED(id); qFatal("Not implemented"); return false; } private: KisStrokesQueue *q; }; struct Q_DECL_HIDDEN KisStrokesQueue::Private { Private(KisStrokesQueue *_q) : q(_q), openedStrokesCounter(0), needsExclusiveAccess(false), wrapAroundModeSupported(false), balancingRatioOverride(-1.0), currentStrokeLoaded(false), lodNNeedsSynchronization(true), desiredLevelOfDetail(0), nextDesiredLevelOfDetail(0), lodNStrokesFacade(_q), lodNPostExecutionUndoAdapter(&lodNUndoStore, &lodNStrokesFacade) {} KisStrokesQueue *q; StrokesQueue strokesQueue; int openedStrokesCounter; bool needsExclusiveAccess; bool wrapAroundModeSupported; qreal balancingRatioOverride; bool currentStrokeLoaded; bool lodNNeedsSynchronization; int desiredLevelOfDetail; int nextDesiredLevelOfDetail; QMutex mutex; KisLodSyncStrokeStrategyFactory lod0ToNStrokeStrategyFactory; KisSuspendResumeStrategyFactory suspendUpdatesStrokeStrategyFactory; KisSuspendResumeStrategyFactory resumeUpdatesStrokeStrategyFactory; KisSurrogateUndoStore lodNUndoStore; LodNUndoStrokesFacade lodNStrokesFacade; KisPostExecutionUndoAdapter lodNPostExecutionUndoAdapter; void cancelForgettableStrokes(); void startLod0ToNStroke(int levelOfDetail, bool forgettable); bool canUseLodN() const; StrokesQueueIterator findNewLod0Pos(); StrokesQueueIterator findNewLodNPos(KisStrokeSP lodN); bool shouldWrapInSuspendUpdatesStroke() const; void switchDesiredLevelOfDetail(bool forced); bool hasUnfinishedStrokes() const; void tryClearUndoOnStrokeCompletion(KisStrokeSP finishingStroke); }; KisStrokesQueue::KisStrokesQueue() : m_d(new Private(this)) { } KisStrokesQueue::~KisStrokesQueue() { Q_FOREACH (KisStrokeSP stroke, m_d->strokesQueue) { stroke->cancelStroke(); } delete m_d; } template typename StrokesQueue::iterator executeStrokePair(const StrokePair &pair, StrokesQueue &queue, typename StrokesQueue::iterator it, KisStroke::Type type, int levelOfDetail, KisStrokesQueueMutatedJobInterface *mutatedJobsInterface) { KisStrokeStrategy *strategy = pair.first; QList jobsData = pair.second; KisStrokeSP stroke(new KisStroke(strategy, type, levelOfDetail)); - strategy->setCancelStrokeId(stroke); - strategy->setMutatedJobsInterface(mutatedJobsInterface); + strategy->setMutatedJobsInterface(mutatedJobsInterface, stroke); it = queue.insert(it, stroke); Q_FOREACH (KisStrokeJobData *jobData, jobsData) { stroke->addJob(jobData); } stroke->endStroke(); return it; } void KisStrokesQueue::Private::startLod0ToNStroke(int levelOfDetail, bool forgettable) { // precondition: lock held! // precondition: lod > 0 KIS_ASSERT_RECOVER_RETURN(levelOfDetail); if (!this->lod0ToNStrokeStrategyFactory) return; KisLodSyncPair syncPair = this->lod0ToNStrokeStrategyFactory(forgettable); executeStrokePair(syncPair, this->strokesQueue, this->strokesQueue.end(), KisStroke::LODN, levelOfDetail, q); this->lodNNeedsSynchronization = false; } void KisStrokesQueue::Private::cancelForgettableStrokes() { if (!strokesQueue.isEmpty() && !hasUnfinishedStrokes()) { Q_FOREACH (KisStrokeSP stroke, strokesQueue) { KIS_ASSERT_RECOVER_NOOP(stroke->isEnded()); if (stroke->canForgetAboutMe()) { stroke->cancelStroke(); } } } } bool KisStrokesQueue::Private::canUseLodN() const { Q_FOREACH (KisStrokeSP stroke, strokesQueue) { if (stroke->type() == KisStroke::LEGACY) { return false; } } return true; } bool KisStrokesQueue::Private::shouldWrapInSuspendUpdatesStroke() const { Q_FOREACH (KisStrokeSP stroke, strokesQueue) { if (stroke->isCancelled()) continue; if (stroke->type() == KisStroke::RESUME) { return false; } } return true; } StrokesQueueIterator KisStrokesQueue::Private::findNewLod0Pos() { StrokesQueueIterator it = strokesQueue.begin(); StrokesQueueIterator end = strokesQueue.end(); for (; it != end; ++it) { if ((*it)->isCancelled()) continue; if ((*it)->type() == KisStroke::RESUME) { return it; } } return it; } StrokesQueueIterator KisStrokesQueue::Private::findNewLodNPos(KisStrokeSP lodN) { StrokesQueueIterator it = strokesQueue.begin(); StrokesQueueIterator end = strokesQueue.end(); for (; it != end; ++it) { if ((*it)->isCancelled()) continue; if ((*it)->type() == KisStroke::LOD0 || (*it)->type() == KisStroke::SUSPEND || (*it)->type() == KisStroke::RESUME) { if (it != end && it == strokesQueue.begin()) { KisStrokeSP head = *it; if (head->supportsSuspension()) { head->suspendStroke(lodN); } } return it; } } return it; } KisStrokeId KisStrokesQueue::startLodNUndoStroke(KisStrokeStrategy *strokeStrategy) { QMutexLocker locker(&m_d->mutex); KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->lodNNeedsSynchronization); KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->desiredLevelOfDetail > 0); KisStrokeSP buddy(new KisStroke(strokeStrategy, KisStroke::LODN, m_d->desiredLevelOfDetail)); - strokeStrategy->setCancelStrokeId(buddy); - strokeStrategy->setMutatedJobsInterface(this); + strokeStrategy->setMutatedJobsInterface(this, buddy); m_d->strokesQueue.insert(m_d->findNewLodNPos(buddy), buddy); KisStrokeId id(buddy); m_d->openedStrokesCounter++; return id; } KisStrokeId KisStrokesQueue::startStroke(KisStrokeStrategy *strokeStrategy) { QMutexLocker locker(&m_d->mutex); KisStrokeSP stroke; KisStrokeStrategy* lodBuddyStrategy; m_d->cancelForgettableStrokes(); if (m_d->desiredLevelOfDetail && m_d->canUseLodN() && (lodBuddyStrategy = strokeStrategy->createLodClone(m_d->desiredLevelOfDetail))) { if (m_d->lodNNeedsSynchronization) { m_d->startLod0ToNStroke(m_d->desiredLevelOfDetail, false); } stroke = KisStrokeSP(new KisStroke(strokeStrategy, KisStroke::LOD0, 0)); KisStrokeSP buddy(new KisStroke(lodBuddyStrategy, KisStroke::LODN, m_d->desiredLevelOfDetail)); - lodBuddyStrategy->setCancelStrokeId(buddy); - lodBuddyStrategy->setMutatedJobsInterface(this); + lodBuddyStrategy->setMutatedJobsInterface(this, buddy); stroke->setLodBuddy(buddy); m_d->strokesQueue.insert(m_d->findNewLodNPos(buddy), buddy); if (m_d->shouldWrapInSuspendUpdatesStroke()) { KisSuspendResumePair suspendPair = m_d->suspendUpdatesStrokeStrategyFactory(); KisSuspendResumePair resumePair = m_d->resumeUpdatesStrokeStrategyFactory(); StrokesQueueIterator it = m_d->findNewLod0Pos(); it = executeStrokePair(resumePair, m_d->strokesQueue, it, KisStroke::RESUME, 0, this); it = m_d->strokesQueue.insert(it, stroke); it = executeStrokePair(suspendPair, m_d->strokesQueue, it, KisStroke::SUSPEND, 0, this); } else { m_d->strokesQueue.insert(m_d->findNewLod0Pos(), stroke); } } else { stroke = KisStrokeSP(new KisStroke(strokeStrategy, KisStroke::LEGACY, 0)); m_d->strokesQueue.enqueue(stroke); } KisStrokeId id(stroke); - strokeStrategy->setCancelStrokeId(id); - strokeStrategy->setMutatedJobsInterface(this); + strokeStrategy->setMutatedJobsInterface(this, id); m_d->openedStrokesCounter++; if (stroke->type() == KisStroke::LEGACY) { m_d->lodNNeedsSynchronization = true; } return id; } void KisStrokesQueue::addJob(KisStrokeId id, KisStrokeJobData *data) { QMutexLocker locker(&m_d->mutex); KisStrokeSP stroke = id.toStrongRef(); KIS_SAFE_ASSERT_RECOVER_RETURN(stroke); KisStrokeSP buddy = stroke->lodBuddy(); if (buddy) { KisStrokeJobData *clonedData = data->createLodClone(buddy->worksOnLevelOfDetail()); KIS_ASSERT_RECOVER_RETURN(clonedData); buddy->addJob(clonedData); } stroke->addJob(data); } void KisStrokesQueue::addMutatedJobs(KisStrokeId id, const QVector list) { QMutexLocker locker(&m_d->mutex); KisStrokeSP stroke = id.toStrongRef(); KIS_SAFE_ASSERT_RECOVER_RETURN(stroke); stroke->addMutatedJobs(list); } void KisStrokesQueue::endStroke(KisStrokeId id) { QMutexLocker locker(&m_d->mutex); KisStrokeSP stroke = id.toStrongRef(); KIS_SAFE_ASSERT_RECOVER_RETURN(stroke); stroke->endStroke(); m_d->openedStrokesCounter--; KisStrokeSP buddy = stroke->lodBuddy(); if (buddy) { buddy->endStroke(); } } bool KisStrokesQueue::cancelStroke(KisStrokeId id) { QMutexLocker locker(&m_d->mutex); KisStrokeSP stroke = id.toStrongRef(); if(stroke) { stroke->cancelStroke(); m_d->openedStrokesCounter--; KisStrokeSP buddy = stroke->lodBuddy(); if (buddy) { buddy->cancelStroke(); } } return stroke; } bool KisStrokesQueue::Private::hasUnfinishedStrokes() const { Q_FOREACH (KisStrokeSP stroke, strokesQueue) { if (!stroke->isEnded()) { return true; } } return false; } bool KisStrokesQueue::tryCancelCurrentStrokeAsync() { bool anythingCanceled = false; QMutexLocker locker(&m_d->mutex); /** * We cancel only ended strokes. This is done to avoid * handling dangling pointers problem (KisStrokeId). The owner * of a stroke will cancel the stroke itself if needed. */ if (!m_d->strokesQueue.isEmpty() && !m_d->hasUnfinishedStrokes()) { anythingCanceled = true; Q_FOREACH (KisStrokeSP currentStroke, m_d->strokesQueue) { KIS_ASSERT_RECOVER_NOOP(currentStroke->isEnded()); currentStroke->cancelStroke(); // we shouldn't cancel buddies... if (currentStroke->type() == KisStroke::LOD0) { /** * If the buddy has already finished, we cannot undo it because * it doesn't store any undo data. Therefore we just regenerate * the LOD caches. */ m_d->lodNNeedsSynchronization = true; } } } /** * NOTE: We do not touch the openedStrokesCounter here since * we work with closed id's only here */ return anythingCanceled; } UndoResult KisStrokesQueue::tryUndoLastStrokeAsync() { UndoResult result = UNDO_FAIL; QMutexLocker locker(&m_d->mutex); std::reverse_iterator it(m_d->strokesQueue.constEnd()); std::reverse_iterator end(m_d->strokesQueue.constBegin()); KisStrokeSP lastStroke; KisStrokeSP lastBuddy; bool buddyFound = false; for (; it != end; ++it) { if ((*it)->type() == KisStroke::LEGACY) { break; } if (!lastStroke && (*it)->type() == KisStroke::LOD0 && !(*it)->isCancelled()) { lastStroke = *it; lastBuddy = lastStroke->lodBuddy(); KIS_SAFE_ASSERT_RECOVER(lastBuddy) { lastStroke.clear(); lastBuddy.clear(); break; } } KIS_SAFE_ASSERT_RECOVER(!lastStroke || *it == lastBuddy || (*it)->type() != KisStroke::LODN) { lastStroke.clear(); lastBuddy.clear(); break; } if (lastStroke && *it == lastBuddy) { KIS_SAFE_ASSERT_RECOVER(lastBuddy->type() == KisStroke::LODN) { lastStroke.clear(); lastBuddy.clear(); break; } buddyFound = true; break; } } if (!lastStroke) return UNDO_FAIL; if (!lastStroke->isEnded()) return UNDO_FAIL; if (lastStroke->isCancelled()) return UNDO_FAIL; KIS_SAFE_ASSERT_RECOVER_NOOP(!buddyFound || lastStroke->isCancelled() == lastBuddy->isCancelled()); KIS_SAFE_ASSERT_RECOVER_NOOP(lastBuddy->isEnded()); if (!lastStroke->canCancel()) { return UNDO_WAIT; } lastStroke->cancelStroke(); if (buddyFound && lastBuddy->canCancel()) { lastBuddy->cancelStroke(); } else { // TODO: assert that checks that there is no other lodn strokes locker.unlock(); m_d->lodNUndoStore.undo(); m_d->lodNUndoStore.purgeRedoState(); locker.relock(); } result = UNDO_OK; return result; } void KisStrokesQueue::Private::tryClearUndoOnStrokeCompletion(KisStrokeSP finishingStroke) { if (finishingStroke->type() != KisStroke::RESUME) return; bool hasResumeStrokes = false; bool hasLod0Strokes = false; Q_FOREACH (KisStrokeSP stroke, strokesQueue) { if (stroke == finishingStroke) continue; hasLod0Strokes |= stroke->type() == KisStroke::LOD0; hasResumeStrokes |= stroke->type() == KisStroke::RESUME; } KIS_SAFE_ASSERT_RECOVER_NOOP(!hasLod0Strokes || hasResumeStrokes); if (!hasResumeStrokes && !hasLod0Strokes) { lodNUndoStore.clear(); } } void KisStrokesQueue::processQueue(KisUpdaterContext &updaterContext, bool externalJobsPending) { updaterContext.lock(); m_d->mutex.lock(); while(updaterContext.hasSpareThread() && processOneJob(updaterContext, externalJobsPending)); m_d->mutex.unlock(); updaterContext.unlock(); } bool KisStrokesQueue::needsExclusiveAccess() const { return m_d->needsExclusiveAccess; } bool KisStrokesQueue::wrapAroundModeSupported() const { return m_d->wrapAroundModeSupported; } qreal KisStrokesQueue::balancingRatioOverride() const { return m_d->balancingRatioOverride; } bool KisStrokesQueue::isEmpty() const { QMutexLocker locker(&m_d->mutex); return m_d->strokesQueue.isEmpty(); } qint32 KisStrokesQueue::sizeMetric() const { QMutexLocker locker(&m_d->mutex); if(m_d->strokesQueue.isEmpty()) return 0; // just a rough approximation return qMax(1, m_d->strokesQueue.head()->numJobs()) * m_d->strokesQueue.size(); } void KisStrokesQueue::Private::switchDesiredLevelOfDetail(bool forced) { if (forced || nextDesiredLevelOfDetail != desiredLevelOfDetail) { Q_FOREACH (KisStrokeSP stroke, strokesQueue) { if (stroke->type() != KisStroke::LEGACY) return; } const bool forgettable = forced && !lodNNeedsSynchronization && desiredLevelOfDetail == nextDesiredLevelOfDetail; desiredLevelOfDetail = nextDesiredLevelOfDetail; lodNNeedsSynchronization |= !forgettable; if (desiredLevelOfDetail) { startLod0ToNStroke(desiredLevelOfDetail, forgettable); } } } void KisStrokesQueue::explicitRegenerateLevelOfDetail() { QMutexLocker locker(&m_d->mutex); m_d->switchDesiredLevelOfDetail(true); } void KisStrokesQueue::setDesiredLevelOfDetail(int lod) { QMutexLocker locker(&m_d->mutex); if (lod == m_d->nextDesiredLevelOfDetail) return; m_d->nextDesiredLevelOfDetail = lod; m_d->switchDesiredLevelOfDetail(false); } void KisStrokesQueue::notifyUFOChangedImage() { QMutexLocker locker(&m_d->mutex); m_d->lodNNeedsSynchronization = true; } void KisStrokesQueue::debugDumpAllStrokes() { QMutexLocker locker(&m_d->mutex); qDebug() <<"==="; Q_FOREACH (KisStrokeSP stroke, m_d->strokesQueue) { qDebug() << ppVar(stroke->name()) << ppVar(stroke->type()) << ppVar(stroke->numJobs()) << ppVar(stroke->isInitialized()) << ppVar(stroke->isCancelled()); } qDebug() <<"==="; } void KisStrokesQueue::setLod0ToNStrokeStrategyFactory(const KisLodSyncStrokeStrategyFactory &factory) { m_d->lod0ToNStrokeStrategyFactory = factory; } void KisStrokesQueue::setSuspendUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyFactory &factory) { m_d->suspendUpdatesStrokeStrategyFactory = factory; } void KisStrokesQueue::setResumeUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyFactory &factory) { m_d->resumeUpdatesStrokeStrategyFactory = factory; } KisPostExecutionUndoAdapter *KisStrokesQueue::lodNPostExecutionUndoAdapter() const { return &m_d->lodNPostExecutionUndoAdapter; } KUndo2MagicString KisStrokesQueue::currentStrokeName() const { QMutexLocker locker(&m_d->mutex); if(m_d->strokesQueue.isEmpty()) return KUndo2MagicString(); return m_d->strokesQueue.head()->name(); } bool KisStrokesQueue::hasOpenedStrokes() const { QMutexLocker locker(&m_d->mutex); return m_d->openedStrokesCounter; } bool KisStrokesQueue::processOneJob(KisUpdaterContext &updaterContext, bool externalJobsPending) { if(m_d->strokesQueue.isEmpty()) return false; bool result = false; const int levelOfDetail = updaterContext.currentLevelOfDetail(); const KisUpdaterContextSnapshotEx snapshot = updaterContext.getContextSnapshotEx(); const bool hasStrokeJobs = !(snapshot == ContextEmpty || snapshot == HasMergeJob); const bool hasMergeJobs = snapshot & HasMergeJob; if(checkStrokeState(hasStrokeJobs, levelOfDetail) && checkExclusiveProperty(hasMergeJobs, hasStrokeJobs) && checkSequentialProperty(snapshot, externalJobsPending)) { KisStrokeSP stroke = m_d->strokesQueue.head(); updaterContext.addStrokeJob(stroke->popOneJob()); result = true; } return result; } bool KisStrokesQueue::checkStrokeState(bool hasStrokeJobsRunning, int runningLevelOfDetail) { KisStrokeSP stroke = m_d->strokesQueue.head(); bool result = false; /** * We cannot start/continue a stroke if its LOD differs from * the one that is running on CPU */ bool hasLodCompatibility = checkLevelOfDetailProperty(runningLevelOfDetail); bool hasJobs = stroke->hasJobs(); /** * The stroke may be cancelled very fast. In this case it will * end up in the state: * * !stroke->isInitialized() && stroke->isEnded() && !stroke->hasJobs() * * This means that !isInitialised() doesn't imply there are any * jobs present. */ if(!stroke->isInitialized() && hasJobs && hasLodCompatibility) { /** * It might happen that the stroke got initialized, but its job was not * started due to some other reasons like exclusivity. Therefore the * stroke might end up in loaded, but uninitialized state. */ if (!m_d->currentStrokeLoaded) { m_d->needsExclusiveAccess = stroke->isExclusive(); m_d->wrapAroundModeSupported = stroke->supportsWrapAroundMode(); m_d->balancingRatioOverride = stroke->balancingRatioOverride(); m_d->currentStrokeLoaded = true; } result = true; } else if(hasJobs && hasLodCompatibility) { /** * If the stroke has no initialization phase, then it can * arrive here unloaded. */ if (!m_d->currentStrokeLoaded) { m_d->needsExclusiveAccess = stroke->isExclusive(); m_d->wrapAroundModeSupported = stroke->supportsWrapAroundMode(); m_d->balancingRatioOverride = stroke->balancingRatioOverride(); m_d->currentStrokeLoaded = true; } result = true; } else if(stroke->isEnded() && !hasJobs && !hasStrokeJobsRunning) { m_d->tryClearUndoOnStrokeCompletion(stroke); m_d->strokesQueue.dequeue(); // deleted by shared pointer m_d->needsExclusiveAccess = false; m_d->wrapAroundModeSupported = false; m_d->balancingRatioOverride = -1.0; m_d->currentStrokeLoaded = false; m_d->switchDesiredLevelOfDetail(false); if(!m_d->strokesQueue.isEmpty()) { result = checkStrokeState(false, runningLevelOfDetail); } } return result; } bool KisStrokesQueue::checkExclusiveProperty(bool hasMergeJobs, bool hasStrokeJobs) { Q_UNUSED(hasStrokeJobs); if(!m_d->strokesQueue.head()->isExclusive()) return true; return hasMergeJobs == 0; } bool KisStrokesQueue::checkSequentialProperty(KisUpdaterContextSnapshotEx snapshot, bool externalJobsPending) { KisStrokeSP stroke = m_d->strokesQueue.head(); if (snapshot & HasSequentialJob || snapshot & HasBarrierJob) { return false; } KisStrokeJobData::Sequentiality nextSequentiality = stroke->nextJobSequentiality(); if (nextSequentiality == KisStrokeJobData::UNIQUELY_CONCURRENT && snapshot & HasUniquelyConcurrentJob) { return false; } if (nextSequentiality == KisStrokeJobData::SEQUENTIAL && (snapshot & HasUniquelyConcurrentJob || snapshot & HasConcurrentJob)) { return false; } if (nextSequentiality == KisStrokeJobData::BARRIER && (snapshot & HasUniquelyConcurrentJob || snapshot & HasConcurrentJob || snapshot & HasMergeJob || externalJobsPending)) { return false; } return true; } bool KisStrokesQueue::checkLevelOfDetailProperty(int runningLevelOfDetail) { KisStrokeSP stroke = m_d->strokesQueue.head(); return runningLevelOfDetail < 0 || stroke->worksOnLevelOfDetail() == runningLevelOfDetail; } diff --git a/libs/image/kis_update_job_item.h b/libs/image/kis_update_job_item.h index 3a6f41b27c..fe4dce18d5 100644 --- a/libs/image/kis_update_job_item.h +++ b/libs/image/kis_update_job_item.h @@ -1,271 +1,265 @@ /* * 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_UPDATE_JOB_ITEM_H #define __KIS_UPDATE_JOB_ITEM_H #include #include #include #include "kis_stroke_job.h" #include "kis_spontaneous_job.h" #include "kis_base_rects_walker.h" #include "kis_async_merger.h" #include "kis_updater_context.h" //#define DEBUG_JOBS_SEQUENCE class KisUpdateJobItem : public QObject, public QRunnable { Q_OBJECT public: enum class Type : int { EMPTY = 0, WAITING, MERGE, STROKE, SPONTANEOUS }; public: KisUpdateJobItem(KisUpdaterContext *updaterContext) - : m_updaterContext(updaterContext), - m_atomicType(Type::EMPTY), - m_runnableJob(0) + : m_updaterContext(updaterContext) { setAutoDelete(false); KIS_SAFE_ASSERT_RECOVER_NOOP(m_atomicType.is_lock_free()); } ~KisUpdateJobItem() override { delete m_runnableJob; } void run() override { if (!isRunning()) return; /** * Here we break the idea of QThreadPool a bit. Ideally, we should split the * jobs into distinct QRunnable objects and pass all of them to QThreadPool. * That is a nice idea, but it doesn't work well when the jobs are small enough * and the number of available cores is high (>4 cores). It this case the * threads just tend to execute the job very quickly and go to sleep, which is * an expensive operation. * * To overcome this problem we try to bulk-process the jobs. In sigJobFinished() * signal (which is DirectConnection), the context may add the job to ourselves(!!!), * so we switch from "done" state into "running" again. */ while (1) { KIS_SAFE_ASSERT_RECOVER_RETURN(isRunning()); if(m_exclusive) { m_updaterContext->m_exclusiveJobLock.lockForWrite(); } else { m_updaterContext->m_exclusiveJobLock.lockForRead(); } if(m_atomicType == Type::MERGE) { runMergeJob(); } else { KIS_ASSERT(m_atomicType == Type::STROKE || m_atomicType == Type::SPONTANEOUS); if (m_runnableJob) { #ifdef DEBUG_JOBS_SEQUENCE if (m_atomicType == Type::STROKE) { qDebug() << "running: stroke" << m_runnableJob->debugName(); } else if (m_atomicType == Type::SPONTANEOUS) { qDebug() << "running: spont " << m_runnableJob->debugName(); } else { qDebug() << "running: unkn. " << m_runnableJob->debugName(); } #endif m_runnableJob->run(); } } setDone(); m_updaterContext->doSomeUsefulWork(); // may flip the current state from Waiting -> Running again m_updaterContext->jobFinished(); m_updaterContext->m_exclusiveJobLock.unlock(); // try to exit the loop. Please note, that no one can flip the state from // WAITING to EMPTY except ourselves! Type expectedValue = Type::WAITING; if (m_atomicType.compare_exchange_strong(expectedValue, Type::EMPTY)) { break; } } } inline void runMergeJob() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_atomicType == Type::MERGE); KIS_SAFE_ASSERT_RECOVER_RETURN(m_walker); // dbgKrita << "Executing merge job" << m_walker->changeRect() // << "on thread" << QThread::currentThreadId(); #ifdef DEBUG_JOBS_SEQUENCE qDebug() << "running: merge " << m_walker->startNode() << m_walker->changeRect(); #endif m_merger.startMerge(*m_walker); QRect changeRect = m_walker->changeRect(); m_updaterContext->continueUpdate(changeRect); } // return true if the thread should actually be started inline bool setWalker(KisBaseRectsWalkerSP walker) { KIS_ASSERT(m_atomicType <= Type::WAITING); m_accessRect = walker->accessRect(); m_changeRect = walker->changeRect(); m_walker = walker; m_exclusive = false; m_runnableJob = 0; const Type oldState = m_atomicType.exchange(Type::MERGE); return oldState == Type::EMPTY; } // return true if the thread should actually be started inline bool setStrokeJob(KisStrokeJob *strokeJob) { KIS_ASSERT(m_atomicType <= Type::WAITING); m_runnableJob = strokeJob; m_strokeJobSequentiality = strokeJob->sequentiality(); m_exclusive = strokeJob->isExclusive(); m_walker = 0; m_accessRect = m_changeRect = QRect(); const Type oldState = m_atomicType.exchange(Type::STROKE); return oldState == Type::EMPTY; } // return true if the thread should actually be started inline bool setSpontaneousJob(KisSpontaneousJob *spontaneousJob) { KIS_ASSERT(m_atomicType <= Type::WAITING); m_runnableJob = spontaneousJob; m_exclusive = spontaneousJob->isExclusive(); m_walker = 0; m_accessRect = m_changeRect = QRect(); const Type oldState = m_atomicType.exchange(Type::SPONTANEOUS); return oldState == Type::EMPTY; } inline void setDone() { m_walker = 0; delete m_runnableJob; m_runnableJob = 0; m_atomicType = Type::WAITING; } inline bool isRunning() const { return m_atomicType >= Type::MERGE; } inline Type type() const { return m_atomicType; } inline const QRect& accessRect() const { return m_accessRect; } inline const QRect& changeRect() const { return m_changeRect; } inline KisStrokeJobData::Sequentiality strokeJobSequentiality() const { return m_strokeJobSequentiality; } private: /** * Open walker and stroke job for the testing suite. * Please, do not use it in production code. */ friend class KisTestableUpdaterContext; friend class KisSimpleUpdateQueueTest; friend class KisStrokesQueueTest; friend class KisUpdateSchedulerTest; friend class KisUpdaterContext; inline KisBaseRectsWalkerSP walker() const { return m_walker; } inline KisStrokeJob* strokeJob() const { KisStrokeJob *job = dynamic_cast(m_runnableJob); Q_ASSERT(job); return job; } inline void testingSetDone() { setDone(); } private: - KisUpdaterContext *m_updaterContext; - - bool m_exclusive; - - std::atomic m_atomicType; - + KisUpdaterContext *m_updaterContext {0}; + bool m_exclusive {false}; + std::atomic m_atomicType {Type::EMPTY}; volatile KisStrokeJobData::Sequentiality m_strokeJobSequentiality; /** * Runnable jobs part * The job is owned by the context and deleted after completion */ - KisRunnableWithDebugName *m_runnableJob; + KisRunnableWithDebugName *m_runnableJob {0}; /** * Merge jobs part */ - KisBaseRectsWalkerSP m_walker; KisAsyncMerger m_merger; /** * These rects cache actual values from the walker * to eliminate concurrent access to a walker structure */ QRect m_accessRect; QRect m_changeRect; }; #endif /* __KIS_UPDATE_JOB_ITEM_H */ diff --git a/libs/image/layerstyles/kis_ls_utils.cpp b/libs/image/layerstyles/kis_ls_utils.cpp index 93b606ef05..4422016f11 100644 --- a/libs/image/layerstyles/kis_ls_utils.cpp +++ b/libs/image/layerstyles/kis_ls_utils.cpp @@ -1,591 +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); + 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(overlayRect.x(), overlayRect.y()); while (noiseIt.nextPixel() && srcIt.nextPixel()) { int itX = noiseIt.x(); int itY = noiseIt.y(); int x = itX + (*noiseIt.rawDataConst() >> 4) - 8; int y = itY + (*noiseIt.rawDataConst() & 0x0F) - 8; x = (x + itX) >> 1; y = (y + itY) >> 1; dstIt->moveTo(x, y); quint8 dstAlpha = *dstIt->rawData(); quint8 srcAlpha = *srcIt.rawDataConst(); int value = qMin(255, dstAlpha + srcAlpha); *dstIt->rawData() = value; } noise = noise * 255 / 100; KisPainter gc(selection); gc.setOpacity(noise); gc.setCompositeOp(COMPOSITE_COPY); gc.bitBlt(applyRect.topLeft(), randomOverlay, applyRect); } //const int FULL_PERCENT_RANGE = 100; void adjustRange(KisPixelSelectionSP selection, const QRect &applyRect, const int range) { KIS_ASSERT_RECOVER_RETURN(range >= 1 && range <= 100); quint8 rangeTable[256]; for(int i = 0; i < 256; i ++) { quint8 value = i * 100 / range; rangeTable[i] = qMin(value, quint8(255)); } KisSequentialIterator dstIt(selection, applyRect); while (dstIt.nextPixel()) { quint8 *pixelPtr = dstIt.rawData(); *pixelPtr = rangeTable[*pixelPtr]; } } void applyContourCorrection(KisPixelSelectionSP selection, const QRect &applyRect, const quint8 *lookup_table, bool antiAliased, bool edgeHidden) { quint8 contour[PSD_LOOKUP_TABLE_SIZE] = { 0x00, 0x0b, 0x16, 0x21, 0x2c, 0x37, 0x42, 0x4d, 0x58, 0x63, 0x6e, 0x79, 0x84, 0x8f, 0x9a, 0xa5, 0xb0, 0xbb, 0xc6, 0xd1, 0xdc, 0xf2, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; if (edgeHidden) { if (antiAliased) { for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) { contour[i] = contour[i] * lookup_table[i] >> 8; } } else { for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) { contour[i] = contour[i] * lookup_table[(int)((int)(i / 2.55) * 2.55 + 0.5)] >> 8; } } } else { if (antiAliased) { for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) { contour[i] = lookup_table[i]; } } else { for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) { contour[i] = lookup_table[(int)((int)(i / 2.55) * 2.55 + 0.5)]; } } } KisSequentialIterator dstIt(selection, applyRect); while (dstIt.nextPixel()) { quint8 *pixelPtr = dstIt.rawData(); *pixelPtr = contour[*pixelPtr]; } } void knockOutSelection(KisPixelSelectionSP selection, KisPixelSelectionSP knockOutSelection, const QRect &srcRect, const QRect &dstRect, const QRect &totalNeedRect, const bool knockOutInverted) { KIS_ASSERT_RECOVER_RETURN(knockOutSelection); QRect knockOutRect = !knockOutInverted ? srcRect : totalNeedRect; knockOutRect &= dstRect; KisPainter gc(selection); gc.setCompositeOp(COMPOSITE_ERASE); gc.bitBlt(knockOutRect.topLeft(), knockOutSelection, knockOutRect); } void fillPattern(KisPaintDeviceSP fillDevice, const QRect &applyRect, KisLayerStyleFilterEnvironment *env, int scale, KoPattern *pattern, int horizontalPhase, int verticalPhase, bool alignWithLayer) { if (scale != 100) { warnKrita << "KisLsOverlayFilter::applyOverlay(): Pattern scaling is NOT implemented!"; } KIS_SAFE_ASSERT_RECOVER_RETURN(pattern); QSize psize(pattern->width(), pattern->height()); QPoint patternOffset(qreal(psize.width()) * horizontalPhase / 100, qreal(psize.height()) * verticalPhase / 100); const QRect boundsRect = alignWithLayer ? env->layerBounds() : env->defaultBounds(); patternOffset += boundsRect.topLeft(); patternOffset.rx() %= psize.width(); patternOffset.ry() %= psize.height(); QRect fillRect = applyRect | applyRect.translated(patternOffset); KisFillPainter gc(fillDevice); gc.fillRect(fillRect.x(), fillRect.y(), fillRect.width(), fillRect.height(), pattern, -patternOffset); gc.end(); } void fillOverlayDevice(KisPaintDeviceSP fillDevice, const QRect &applyRect, const psd_layer_effects_overlay_base *config, KisLayerStyleFilterEnvironment *env) { if (config->fillType() == psd_fill_solid_color) { KoColor color(config->color(), fillDevice->colorSpace()); fillDevice->setDefaultPixel(color); } else if (config->fillType() == psd_fill_pattern) { fillPattern(fillDevice, applyRect, env, config->scale(), config->pattern(), config->horizontalPhase(), config->verticalPhase(), config->alignWithLayer()); } else if (config->fillType() == psd_fill_gradient) { const QRect boundsRect = config->alignWithLayer() ? env->layerBounds() : env->defaultBounds(); QPoint center = boundsRect.center(); center += QPoint(boundsRect.width() * config->gradientXOffset() / 100, boundsRect.height() * config->gradientYOffset() / 100); int width = (boundsRect.width() * config->scale() + 100) / 200; int height = (boundsRect.height() * config->scale() + 100) / 200; /* copy paste from libpsd */ int angle = config->angle(); int corner_angle = (int)(atan((qreal)boundsRect.height() / boundsRect.width()) * 180 / M_PI + 0.5); int sign_x = 1; int sign_y = 1; if(angle < 0) { angle += 360; } if (angle >= 90 && angle < 180) { angle = 180 - angle; sign_x = -1; } else if (angle >= 180 && angle < 270) { angle = angle - 180; sign_x = -1; sign_y = -1; } else if (angle >= 270 && angle <= 360) { angle = 360 - angle; sign_y = -1; } int radius_x = 0; int radius_y = 0; if (angle <= corner_angle) { radius_x = width; radius_y = (int)(radius_x * tan(kisDegreesToRadians(qreal(angle))) + 0.5); } else { radius_y = height; radius_x = (int)(radius_y / tan(kisDegreesToRadians(qreal(angle))) + 0.5); } int radius_corner = (int)(std::sqrt((qreal)(radius_x * radius_x + radius_y * radius_y)) + 0.5); /* end of copy paste from libpsd */ KisGradientPainter gc(fillDevice); gc.setGradient(config->gradient().data()); QPointF gradStart; QPointF gradEnd; KisGradientPainter::enumGradientRepeat repeat = KisGradientPainter::GradientRepeatNone; QPoint rectangularOffset(sign_x * radius_x, -sign_y * radius_y); switch(config->style()) { case psd_gradient_style_linear: gc.setGradientShape(KisGradientPainter::GradientShapeLinear); repeat = KisGradientPainter::GradientRepeatNone; gradStart = center - rectangularOffset; gradEnd = center + rectangularOffset; break; case psd_gradient_style_radial: gc.setGradientShape(KisGradientPainter::GradientShapeRadial); repeat = KisGradientPainter::GradientRepeatNone; gradStart = center; gradEnd = center + QPointF(radius_corner, 0); break; case psd_gradient_style_angle: gc.setGradientShape(KisGradientPainter::GradientShapeConical); repeat = KisGradientPainter::GradientRepeatNone; gradStart = center; gradEnd = center + rectangularOffset; break; case psd_gradient_style_reflected: gc.setGradientShape(KisGradientPainter::GradientShapeLinear); repeat = KisGradientPainter::GradientRepeatAlternate; gradStart = center - rectangularOffset; gradEnd = center; break; case psd_gradient_style_diamond: gc.setGradientShape(KisGradientPainter::GradientShapeBiLinear); repeat = KisGradientPainter::GradientRepeatNone; gradStart = center - rectangularOffset; gradEnd = center + rectangularOffset; break; default: qFatal("Gradient Overlay: unknown switch case!"); break; } gc.paintGradient(gradStart, gradEnd, repeat, 0.0, config->reverse(), applyRect); } } void applyFinalSelection(const QString &projectionId, KisSelectionSP baseSelection, KisPaintDeviceSP srcDevice, KisMultipleProjection *dst, const QRect &/*srcRect*/, const QRect &dstRect, const psd_layer_effects_context */*context*/, const psd_layer_effects_shadow_base *config, const KisLayerStyleFilterEnvironment *env) { const KoColor effectColor(config->color(), srcDevice->colorSpace()); const QRect effectRect(dstRect); const QString compositeOp = config->blendMode(); const quint8 opacityU8 = 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/patched_boykov_kolmogorov_max_flow.hpp b/libs/image/lazybrush/patched_boykov_kolmogorov_max_flow.hpp index b2a13e70de..0373186de3 100644 --- a/libs/image/lazybrush/patched_boykov_kolmogorov_max_flow.hpp +++ b/libs/image/lazybrush/patched_boykov_kolmogorov_max_flow.hpp @@ -1,882 +1,882 @@ // Copyright (c) 2006, Stephan Diederich // // This code may be used under either of the following two licences: // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. OF SUCH DAMAGE. // // Or: // // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) +// https://www.boost.org/LICENSE_1_0.txt) #ifndef BOOST_BOYKOV_KOLMOGOROV_MAX_FLOW_HPP #define BOOST_BOYKOV_KOLMOGOROV_MAX_FLOW_HPP #include #include #include #include #include #include #include // for std::min and std::max #include #include #include #include #include #include #include #include // The algorithm impelemented here is described in: // // Boykov, Y., Kolmogorov, V. "An Experimental Comparison of Min-Cut/Max-Flow // Algorithms for Energy Minimization in Vision", In IEEE Transactions on // Pattern Analysis and Machine Intelligence, vol. 26, no. 9, pp. 1124-1137, // Sep 2004. // // For further reading, also see: // // Kolmogorov, V. "Graph Based Algorithms for Scene Reconstruction from Two or // More Views". PhD thesis, Cornell University, Sep 2003. namespace boost { namespace detail { template class bk_max_flow { typedef typename property_traits::value_type tEdgeVal; typedef graph_traits tGraphTraits; typedef typename tGraphTraits::vertex_iterator vertex_iterator; typedef typename tGraphTraits::vertex_descriptor vertex_descriptor; typedef typename tGraphTraits::edge_descriptor edge_descriptor; typedef typename tGraphTraits::edge_iterator edge_iterator; typedef typename tGraphTraits::out_edge_iterator out_edge_iterator; typedef boost::queue tQueue; //queue of vertices, used in adoption-stage typedef typename property_traits::value_type tColorValue; typedef color_traits tColorTraits; typedef typename property_traits::value_type tDistanceVal; public: bk_max_flow(Graph& g, EdgeCapacityMap cap, ResidualCapacityEdgeMap res, ReverseEdgeMap rev, PredecessorMap pre, ColorMap color, DistanceMap dist, IndexMap idx, vertex_descriptor src, vertex_descriptor sink): m_g(g), m_index_map(idx), m_cap_map(cap), m_res_cap_map(res), m_rev_edge_map(rev), m_pre_map(pre), m_tree_map(color), m_dist_map(dist), m_source(src), m_sink(sink), m_active_nodes(), m_in_active_list_vec(num_vertices(g), false), m_in_active_list_map(make_iterator_property_map(m_in_active_list_vec.begin(), m_index_map)), m_has_parent_vec(num_vertices(g), false), m_has_parent_map(make_iterator_property_map(m_has_parent_vec.begin(), m_index_map)), m_time_vec(num_vertices(g), 0), m_time_map(make_iterator_property_map(m_time_vec.begin(), m_index_map)), m_flow(0), m_time(1), m_last_grow_vertex(graph_traits::null_vertex()){ // initialize the color-map with gray-values vertex_iterator vi, v_end; for(boost::tie(vi, v_end) = vertices(m_g); vi != v_end; ++vi){ set_tree(*vi, tColorTraits::gray()); } // Initialize flow to zero which means initializing // the residual capacity equal to the capacity edge_iterator ei, e_end; for(boost::tie(ei, e_end) = edges(m_g); ei != e_end; ++ei) { put(m_res_cap_map, *ei, get(m_cap_map, *ei)); BOOST_ASSERT(get(m_rev_edge_map, get(m_rev_edge_map, *ei)) == *ei); //check if the reverse edge map is build up properly } //init the search trees with the two terminals set_tree(m_source, tColorTraits::black()); set_tree(m_sink, tColorTraits::white()); put(m_time_map, m_source, 1); put(m_time_map, m_sink, 1); } tEdgeVal max_flow(){ //augment direct paths from SOURCE->SINK and SOURCE->VERTEX->SINK augment_direct_paths(); //start the main-loop while(true){ bool path_found; edge_descriptor connecting_edge; boost::tie(connecting_edge, path_found) = grow(); //find a path from source to sink if(!path_found){ //we're finished, no more paths were found break; } ++m_time; augment(connecting_edge); //augment that path adopt(); //rebuild search tree structure } return m_flow; } // the complete class is protected, as we want access to members in // derived test-class (see test/boykov_kolmogorov_max_flow_test.cpp) protected: void augment_direct_paths(){ // in a first step, we augment all direct paths from source->NODE->sink // and additionally paths from source->sink. This improves especially // graphcuts for segmentation, as most of the nodes have source/sink // connects but shouldn't have an impact on other maxflow problems // (this is done in grow() anyway) out_edge_iterator ei, e_end; for(boost::tie(ei, e_end) = out_edges(m_source, m_g); ei != e_end; ++ei){ edge_descriptor from_source = *ei; vertex_descriptor current_node = target(from_source, m_g); if(current_node == m_sink){ tEdgeVal cap = get(m_res_cap_map, from_source); put(m_res_cap_map, from_source, 0); m_flow += cap; continue; } edge_descriptor to_sink; bool is_there; boost::tie(to_sink, is_there) = lookup_edge(current_node, m_sink, m_g); if(is_there){ tEdgeVal cap_from_source = get(m_res_cap_map, from_source); tEdgeVal cap_to_sink = get(m_res_cap_map, to_sink); if(cap_from_source > cap_to_sink){ set_tree(current_node, tColorTraits::black()); add_active_node(current_node); set_edge_to_parent(current_node, from_source); put(m_dist_map, current_node, 1); put(m_time_map, current_node, 1); // add stuff to flow and update residuals. we don't need to // update reverse_edges, as incoming/outgoing edges to/from // source/sink don't count for max-flow put(m_res_cap_map, from_source, get(m_res_cap_map, from_source) - cap_to_sink); put(m_res_cap_map, to_sink, 0); m_flow += cap_to_sink; } else if(cap_to_sink > 0){ set_tree(current_node, tColorTraits::white()); add_active_node(current_node); set_edge_to_parent(current_node, to_sink); put(m_dist_map, current_node, 1); put(m_time_map, current_node, 1); // add stuff to flow and update residuals. we don't need to update // reverse_edges, as incoming/outgoing edges to/from source/sink // don't count for max-flow put(m_res_cap_map, to_sink, get(m_res_cap_map, to_sink) - cap_from_source); put(m_res_cap_map, from_source, 0); m_flow += cap_from_source; } } else if(get(m_res_cap_map, from_source)){ // there is no sink connect, so we can't augment this path, but to // avoid adding m_source to the active nodes, we just activate this // node and set the appropriate things set_tree(current_node, tColorTraits::black()); set_edge_to_parent(current_node, from_source); put(m_dist_map, current_node, 1); put(m_time_map, current_node, 1); add_active_node(current_node); } } for(boost::tie(ei, e_end) = out_edges(m_sink, m_g); ei != e_end; ++ei){ edge_descriptor to_sink = get(m_rev_edge_map, *ei); vertex_descriptor current_node = source(to_sink, m_g); if(get(m_res_cap_map, to_sink)){ set_tree(current_node, tColorTraits::white()); set_edge_to_parent(current_node, to_sink); put(m_dist_map, current_node, 1); put(m_time_map, current_node, 1); add_active_node(current_node); } } } /** * Returns a pair of an edge and a boolean. if the bool is true, the * edge is a connection of a found path from s->t , read "the link" and * source(returnVal, m_g) is the end of the path found in the source-tree * target(returnVal, m_g) is the beginning of the path found in the sink-tree */ std::pair grow(){ BOOST_ASSERT(m_orphans.empty()); vertex_descriptor current_node; while((current_node = get_next_active_node()) != graph_traits::null_vertex()){ //if there is one BOOST_ASSERT(get_tree(current_node) != tColorTraits::gray() && (has_parent(current_node) || current_node == m_source || current_node == m_sink)); if(get_tree(current_node) == tColorTraits::black()){ //source tree growing out_edge_iterator ei, e_end; if(current_node != m_last_grow_vertex){ m_last_grow_vertex = current_node; boost::tie(m_last_grow_edge_it, m_last_grow_edge_end) = out_edges(current_node, m_g); } for(; m_last_grow_edge_it != m_last_grow_edge_end; ++m_last_grow_edge_it) { edge_descriptor out_edge = *m_last_grow_edge_it; if(get(m_res_cap_map, out_edge) > 0){ //check if we have capacity left on this edge vertex_descriptor other_node = target(out_edge, m_g); if(get_tree(other_node) == tColorTraits::gray()){ //it's a free node set_tree(other_node, tColorTraits::black()); //acquire other node to our search tree set_edge_to_parent(other_node, out_edge); //set us as parent put(m_dist_map, other_node, get(m_dist_map, current_node) + 1); //and update the distance-heuristic put(m_time_map, other_node, get(m_time_map, current_node)); add_active_node(other_node); } else if(get_tree(other_node) == tColorTraits::black()) { // we do this to get shorter paths. check if we are nearer to // the source as its parent is if(is_closer_to_terminal(current_node, other_node)){ set_edge_to_parent(other_node, out_edge); put(m_dist_map, other_node, get(m_dist_map, current_node) + 1); put(m_time_map, other_node, get(m_time_map, current_node)); } } else{ BOOST_ASSERT(get_tree(other_node)==tColorTraits::white()); //kewl, found a path from one to the other search tree, return // the connecting edge in src->sink dir return std::make_pair(out_edge, true); } } } //for all out-edges } //source-tree-growing else{ BOOST_ASSERT(get_tree(current_node) == tColorTraits::white()); out_edge_iterator ei, e_end; if(current_node != m_last_grow_vertex){ m_last_grow_vertex = current_node; boost::tie(m_last_grow_edge_it, m_last_grow_edge_end) = out_edges(current_node, m_g); } for(; m_last_grow_edge_it != m_last_grow_edge_end; ++m_last_grow_edge_it){ edge_descriptor in_edge = get(m_rev_edge_map, *m_last_grow_edge_it); if(get(m_res_cap_map, in_edge) > 0){ //check if there is capacity left vertex_descriptor other_node = source(in_edge, m_g); if(get_tree(other_node) == tColorTraits::gray()){ //it's a free node set_tree(other_node, tColorTraits::white()); //acquire that node to our search tree set_edge_to_parent(other_node, in_edge); //set us as parent add_active_node(other_node); //activate that node put(m_dist_map, other_node, get(m_dist_map, current_node) + 1); //set its distance put(m_time_map, other_node, get(m_time_map, current_node));//and time } else if(get_tree(other_node) == tColorTraits::white()){ if(is_closer_to_terminal(current_node, other_node)){ //we are closer to the sink than its parent is, so we "adopt" him set_edge_to_parent(other_node, in_edge); put(m_dist_map, other_node, get(m_dist_map, current_node) + 1); put(m_time_map, other_node, get(m_time_map, current_node)); } } else{ BOOST_ASSERT(get_tree(other_node)==tColorTraits::black()); //kewl, found a path from one to the other search tree, // return the connecting edge in src->sink dir return std::make_pair(in_edge, true); } } } //for all out-edges } //sink-tree growing //all edges of that node are processed, and no more paths were found. // remove if from the front of the active queue finish_node(current_node); } //while active_nodes not empty //no active nodes anymore and no path found, we're done return std::make_pair(edge_descriptor(), false); } /** * augments path from s->t and updates residual graph * source(e, m_g) is the end of the path found in the source-tree * target(e, m_g) is the beginning of the path found in the sink-tree * this phase generates orphans on satured edges, if the attached verts are * from different search-trees orphans are ordered in distance to * sink/source. first the farest from the source are front_inserted into * the orphans list, and after that the sink-tree-orphans are * front_inserted. when going to adoption stage the orphans are popped_front, * and so we process the nearest verts to the terminals first */ void augment(edge_descriptor e) { BOOST_ASSERT(get_tree(target(e, m_g)) == tColorTraits::white()); BOOST_ASSERT(get_tree(source(e, m_g)) == tColorTraits::black()); BOOST_ASSERT(m_orphans.empty()); const tEdgeVal bottleneck = find_bottleneck(e); //now we push the found flow through the path //for each edge we saturate we have to look for the verts that belong to that edge, one of them becomes an orphans //now process the connecting edge put(m_res_cap_map, e, get(m_res_cap_map, e) - bottleneck); BOOST_ASSERT(get(m_res_cap_map, e) >= 0); put(m_res_cap_map, get(m_rev_edge_map, e), get(m_res_cap_map, get(m_rev_edge_map, e)) + bottleneck); //now we follow the path back to the source vertex_descriptor current_node = source(e, m_g); while(current_node != m_source){ edge_descriptor pred = get_edge_to_parent(current_node); const tEdgeVal new_pred_cap = get(m_res_cap_map, pred) - bottleneck; put(m_res_cap_map, pred, new_pred_cap); BOOST_ASSERT(get(m_res_cap_map, pred) >= 0); const edge_descriptor pred_rev = get(m_rev_edge_map, pred); put(m_res_cap_map, pred_rev, get(m_res_cap_map, pred_rev) + bottleneck); if(new_pred_cap == 0){ set_no_parent(current_node); m_orphans.push_front(current_node); } current_node = source(pred, m_g); } //then go forward in the sink-tree current_node = target(e, m_g); while(current_node != m_sink){ edge_descriptor pred = get_edge_to_parent(current_node); const tEdgeVal new_pred_cap = get(m_res_cap_map, pred) - bottleneck; put(m_res_cap_map, pred, new_pred_cap); BOOST_ASSERT(get(m_res_cap_map, pred) >= 0); const edge_descriptor pred_rev = get(m_rev_edge_map, pred); put(m_res_cap_map, pred_rev, get(m_res_cap_map, pred_rev) + bottleneck); if(new_pred_cap == 0){ set_no_parent(current_node); m_orphans.push_front(current_node); } current_node = target(pred, m_g); } //and add it to the max-flow m_flow += bottleneck; } /** * returns the bottleneck of a s->t path (end_of_path is last vertex in * source-tree, begin_of_path is first vertex in sink-tree) */ inline tEdgeVal find_bottleneck(edge_descriptor e){ BOOST_USING_STD_MIN(); tEdgeVal minimum_cap = get(m_res_cap_map, e); vertex_descriptor current_node = source(e, m_g); //first go back in the source tree while(current_node != m_source){ edge_descriptor pred = get_edge_to_parent(current_node); minimum_cap = min BOOST_PREVENT_MACRO_SUBSTITUTION(minimum_cap, get(m_res_cap_map, pred)); current_node = source(pred, m_g); } //then go forward in the sink-tree current_node = target(e, m_g); while(current_node != m_sink){ edge_descriptor pred = get_edge_to_parent(current_node); minimum_cap = min BOOST_PREVENT_MACRO_SUBSTITUTION(minimum_cap, get(m_res_cap_map, pred)); current_node = target(pred, m_g); } return minimum_cap; } /** * rebuild search trees * empty the queue of orphans, and find new parents for them or just drop * them from the search trees */ void adopt(){ while(!m_orphans.empty() || !m_child_orphans.empty()){ vertex_descriptor current_node; if(m_child_orphans.empty()){ //get the next orphan from the main-queue and remove it current_node = m_orphans.front(); m_orphans.pop_front(); } else{ current_node = m_child_orphans.front(); m_child_orphans.pop(); } if(get_tree(current_node) == tColorTraits::black()){ //we're in the source-tree tDistanceVal min_distance = (std::numeric_limits::max)(); edge_descriptor new_parent_edge; out_edge_iterator ei, e_end; for(boost::tie(ei, e_end) = out_edges(current_node, m_g); ei != e_end; ++ei){ const edge_descriptor in_edge = get(m_rev_edge_map, *ei); BOOST_ASSERT(target(in_edge, m_g) == current_node); //we should be the target of this edge if(get(m_res_cap_map, in_edge) > 0){ vertex_descriptor other_node = source(in_edge, m_g); if(get_tree(other_node) == tColorTraits::black() && has_source_connect(other_node)){ if(get(m_dist_map, other_node) < min_distance){ min_distance = get(m_dist_map, other_node); new_parent_edge = in_edge; } } } } if(min_distance != (std::numeric_limits::max)()){ set_edge_to_parent(current_node, new_parent_edge); put(m_dist_map, current_node, min_distance + 1); put(m_time_map, current_node, m_time); } else{ put(m_time_map, current_node, 0); for(boost::tie(ei, e_end) = out_edges(current_node, m_g); ei != e_end; ++ei){ edge_descriptor in_edge = get(m_rev_edge_map, *ei); vertex_descriptor other_node = source(in_edge, m_g); if(get_tree(other_node) == tColorTraits::black() && other_node != m_source){ if(get(m_res_cap_map, in_edge) > 0){ add_active_node(other_node); } if(has_parent(other_node) && source(get_edge_to_parent(other_node), m_g) == current_node){ //we are the parent of that node //it has to find a new parent, too set_no_parent(other_node); m_child_orphans.push(other_node); } } } set_tree(current_node, tColorTraits::gray()); } //no parent found } //source-tree-adoption else{ //now we should be in the sink-tree, check that... BOOST_ASSERT(get_tree(current_node) == tColorTraits::white()); out_edge_iterator ei, e_end; edge_descriptor new_parent_edge; tDistanceVal min_distance = (std::numeric_limits::max)(); for(boost::tie(ei, e_end) = out_edges(current_node, m_g); ei != e_end; ++ei){ const edge_descriptor out_edge = *ei; if(get(m_res_cap_map, out_edge) > 0){ const vertex_descriptor other_node = target(out_edge, m_g); if(get_tree(other_node) == tColorTraits::white() && has_sink_connect(other_node)) if(get(m_dist_map, other_node) < min_distance){ min_distance = get(m_dist_map, other_node); new_parent_edge = out_edge; } } } if(min_distance != (std::numeric_limits::max)()){ set_edge_to_parent(current_node, new_parent_edge); put(m_dist_map, current_node, min_distance + 1); put(m_time_map, current_node, m_time); } else{ put(m_time_map, current_node, 0); for(boost::tie(ei, e_end) = out_edges(current_node, m_g); ei != e_end; ++ei){ const edge_descriptor out_edge = *ei; const vertex_descriptor other_node = target(out_edge, m_g); if(get_tree(other_node) == tColorTraits::white() && other_node != m_sink){ if(get(m_res_cap_map, out_edge) > 0){ add_active_node(other_node); } if(has_parent(other_node) && target(get_edge_to_parent(other_node), m_g) == current_node){ //we were it's parent, so it has to find a new one, too set_no_parent(other_node); m_child_orphans.push(other_node); } } } set_tree(current_node, tColorTraits::gray()); } //no parent found } //sink-tree adoption } //while !orphans.empty() } //adopt /** * return next active vertex if there is one, otherwise a null_vertex */ inline vertex_descriptor get_next_active_node(){ while(true){ if(m_active_nodes.empty()) return graph_traits::null_vertex(); vertex_descriptor v = m_active_nodes.front(); //if it has no parent, this node can't be active (if its not source or sink) if(!has_parent(v) && v != m_source && v != m_sink){ m_active_nodes.pop(); put(m_in_active_list_map, v, false); } else{ BOOST_ASSERT(get_tree(v) == tColorTraits::black() || get_tree(v) == tColorTraits::white()); return v; } } } /** * adds v as an active vertex, but only if its not in the list already */ inline void add_active_node(vertex_descriptor v){ BOOST_ASSERT(get_tree(v) != tColorTraits::gray()); if(get(m_in_active_list_map, v)){ if (m_last_grow_vertex == v) { m_last_grow_vertex = graph_traits::null_vertex(); } return; } else{ put(m_in_active_list_map, v, true); m_active_nodes.push(v); } } /** * finish_node removes a node from the front of the active queue (its called in grow phase, if no more paths can be found using this node) */ inline void finish_node(vertex_descriptor v){ BOOST_ASSERT(m_active_nodes.front() == v); m_active_nodes.pop(); put(m_in_active_list_map, v, false); m_last_grow_vertex = graph_traits::null_vertex(); } /** * removes a vertex from the queue of active nodes (actually this does nothing, * but checks if this node has no parent edge, as this is the criteria for * being no more active) */ inline void remove_active_node(vertex_descriptor v){ (void)v; // disable unused parameter warning BOOST_ASSERT(!has_parent(v)); } /** * returns the search tree of v; tColorValue::black() for source tree, * white() for sink tree, gray() for no tree */ inline tColorValue get_tree(vertex_descriptor v) const { return get(m_tree_map, v); } /** * sets search tree of v; tColorValue::black() for source tree, white() * for sink tree, gray() for no tree */ inline void set_tree(vertex_descriptor v, tColorValue t){ put(m_tree_map, v, t); } /** * returns edge to parent vertex of v; */ inline edge_descriptor get_edge_to_parent(vertex_descriptor v) const{ return get(m_pre_map, v); } /** * returns true if the edge stored in m_pre_map[v] is a valid entry */ inline bool has_parent(vertex_descriptor v) const{ return get(m_has_parent_map, v); } /** * sets edge to parent vertex of v; */ inline void set_edge_to_parent(vertex_descriptor v, edge_descriptor f_edge_to_parent){ BOOST_ASSERT(get(m_res_cap_map, f_edge_to_parent) > 0); put(m_pre_map, v, f_edge_to_parent); put(m_has_parent_map, v, true); } /** * removes the edge to parent of v (this is done by invalidating the * entry an additional map) */ inline void set_no_parent(vertex_descriptor v){ put(m_has_parent_map, v, false); } /** * checks if vertex v has a connect to the sink-vertex (@p m_sink) * @param v the vertex which is checked * @return true if a path to the sink was found, false if not */ inline bool has_sink_connect(vertex_descriptor v){ tDistanceVal current_distance = 0; vertex_descriptor current_vertex = v; while(true){ if(get(m_time_map, current_vertex) == m_time){ //we found a node which was already checked this round. use it for distance calculations current_distance += get(m_dist_map, current_vertex); break; } if(current_vertex == m_sink){ put(m_time_map, m_sink, m_time); break; } if(has_parent(current_vertex)){ //it has a parent, so get it current_vertex = target(get_edge_to_parent(current_vertex), m_g); ++current_distance; } else{ //no path found return false; } } current_vertex=v; while(get(m_time_map, current_vertex) != m_time){ put(m_dist_map, current_vertex, current_distance); --current_distance; put(m_time_map, current_vertex, m_time); current_vertex = target(get_edge_to_parent(current_vertex), m_g); } return true; } /** * checks if vertex v has a connect to the source-vertex (@p m_source) * @param v the vertex which is checked * @return true if a path to the source was found, false if not */ inline bool has_source_connect(vertex_descriptor v){ tDistanceVal current_distance = 0; vertex_descriptor current_vertex = v; while(true){ if(get(m_time_map, current_vertex) == m_time){ //we found a node which was already checked this round. use it for distance calculations current_distance += get(m_dist_map, current_vertex); break; } if(current_vertex == m_source){ put(m_time_map, m_source, m_time); break; } if(has_parent(current_vertex)){ //it has a parent, so get it current_vertex = source(get_edge_to_parent(current_vertex), m_g); ++current_distance; } else{ //no path found return false; } } current_vertex=v; while(get(m_time_map, current_vertex) != m_time){ put(m_dist_map, current_vertex, current_distance); --current_distance; put(m_time_map, current_vertex, m_time); current_vertex = source(get_edge_to_parent(current_vertex), m_g); } return true; } /** * returns true, if p is closer to a terminal than q */ inline bool is_closer_to_terminal(vertex_descriptor p, vertex_descriptor q){ //checks the timestamps first, to build no cycles, and after that the real distance return (get(m_time_map, q) <= get(m_time_map, p) && get(m_dist_map, q) > get(m_dist_map, p)+1); } //////// // member vars //////// Graph& m_g; IndexMap m_index_map; EdgeCapacityMap m_cap_map; ResidualCapacityEdgeMap m_res_cap_map; ReverseEdgeMap m_rev_edge_map; PredecessorMap m_pre_map; //stores paths found in the growth stage ColorMap m_tree_map; //maps each vertex into one of the two search tree or none (gray()) DistanceMap m_dist_map; //stores distance to source/sink nodes vertex_descriptor m_source; vertex_descriptor m_sink; tQueue m_active_nodes; std::vector m_in_active_list_vec; iterator_property_map::iterator, IndexMap> m_in_active_list_map; std::list m_orphans; tQueue m_child_orphans; // we use a second queuqe for child orphans, as they are FIFO processed std::vector m_has_parent_vec; iterator_property_map::iterator, IndexMap> m_has_parent_map; std::vector m_time_vec; //timestamp of each node, used for sink/source-path calculations iterator_property_map::iterator, IndexMap> m_time_map; tEdgeVal m_flow; long m_time; vertex_descriptor m_last_grow_vertex; out_edge_iterator m_last_grow_edge_it; out_edge_iterator m_last_grow_edge_end; }; } //namespace boost::detail /** * non-named-parameter version, given everything * this is the catch all version */ template typename property_traits::value_type boykov_kolmogorov_max_flow(Graph& g, CapacityEdgeMap cap, ResidualCapacityEdgeMap res_cap, ReverseEdgeMap rev_map, PredecessorMap pre_map, ColorMap color, DistanceMap dist, IndexMap idx, typename graph_traits::vertex_descriptor src, typename graph_traits::vertex_descriptor sink) { typedef typename graph_traits::vertex_descriptor vertex_descriptor; typedef typename graph_traits::edge_descriptor edge_descriptor; //as this method is the last one before we instantiate the solver, we do the concept checks here BOOST_CONCEPT_ASSERT(( VertexListGraphConcept )); //to have vertices(), num_vertices(), BOOST_CONCEPT_ASSERT(( EdgeListGraphConcept )); //to have edges() BOOST_CONCEPT_ASSERT(( IncidenceGraphConcept )); //to have source(), target() and out_edges() BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept )); //read flow-values from edges BOOST_CONCEPT_ASSERT(( ReadWritePropertyMapConcept )); //write flow-values to residuals BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept )); //read out reverse edges BOOST_CONCEPT_ASSERT(( ReadWritePropertyMapConcept )); //store predecessor there BOOST_CONCEPT_ASSERT(( ReadWritePropertyMapConcept )); //write corresponding tree BOOST_CONCEPT_ASSERT(( ReadWritePropertyMapConcept )); //write distance to source/sink BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept )); //get index 0...|V|-1 BOOST_ASSERT(num_vertices(g) >= 2 && src != sink); detail::bk_max_flow< Graph, CapacityEdgeMap, ResidualCapacityEdgeMap, ReverseEdgeMap, PredecessorMap, ColorMap, DistanceMap, IndexMap > algo(g, cap, res_cap, rev_map, pre_map, color, dist, idx, src, sink); return algo.max_flow(); } /** * non-named-parameter version, given capacity, residucal_capacity, * reverse_edges, and an index map. */ template typename property_traits::value_type boykov_kolmogorov_max_flow(Graph& g, CapacityEdgeMap cap, ResidualCapacityEdgeMap res_cap, ReverseEdgeMap rev, IndexMap idx, typename graph_traits::vertex_descriptor src, typename graph_traits::vertex_descriptor sink) { typename graph_traits::vertices_size_type n_verts = num_vertices(g); std::vector::edge_descriptor> predecessor_vec(n_verts); std::vector color_vec(n_verts); std::vector::vertices_size_type> distance_vec(n_verts); return boykov_kolmogorov_max_flow( g, cap, res_cap, rev, make_iterator_property_map(predecessor_vec.begin(), idx), make_iterator_property_map(color_vec.begin(), idx), make_iterator_property_map(distance_vec.begin(), idx), idx, src, sink); } /** * non-named-parameter version, some given: capacity, residual_capacity, * reverse_edges, color_map and an index map. Use this if you are interested in * the minimum cut, as the color map provides that info. */ template typename property_traits::value_type boykov_kolmogorov_max_flow(Graph& g, CapacityEdgeMap cap, ResidualCapacityEdgeMap res_cap, ReverseEdgeMap rev, ColorMap color, IndexMap idx, typename graph_traits::vertex_descriptor src, typename graph_traits::vertex_descriptor sink) { typename graph_traits::vertices_size_type n_verts = num_vertices(g); std::vector::edge_descriptor> predecessor_vec(n_verts); std::vector::vertices_size_type> distance_vec(n_verts); return boykov_kolmogorov_max_flow( g, cap, res_cap, rev, make_iterator_property_map(predecessor_vec.begin(), idx), color, make_iterator_property_map(distance_vec.begin(), idx), idx, src, sink); } /** * named-parameter version, some given */ template typename property_traits::const_type>::value_type boykov_kolmogorov_max_flow(Graph& g, typename graph_traits::vertex_descriptor src, typename graph_traits::vertex_descriptor sink, const bgl_named_params& params) { return boykov_kolmogorov_max_flow( g, choose_const_pmap(get_param(params, edge_capacity), g, edge_capacity), choose_pmap(get_param(params, edge_residual_capacity), g, edge_residual_capacity), choose_const_pmap(get_param(params, edge_reverse), g, edge_reverse), choose_pmap(get_param(params, vertex_predecessor), g, vertex_predecessor), choose_pmap(get_param(params, vertex_color), g, vertex_color), choose_pmap(get_param(params, vertex_distance), g, vertex_distance), choose_const_pmap(get_param(params, vertex_index), g, vertex_index), src, sink); } /** * named-parameter version, none given */ template typename property_traits::const_type>::value_type boykov_kolmogorov_max_flow(Graph& g, typename graph_traits::vertex_descriptor src, typename graph_traits::vertex_descriptor sink) { bgl_named_params params(0); // bogus empty param return boykov_kolmogorov_max_flow(g, src, sink, params); } } // namespace boost #endif // BOOST_BOYKOV_KOLMOGOROV_MAX_FLOW_HPP diff --git a/libs/image/processing/kis_assign_profile_processing_visitor.cpp b/libs/image/processing/kis_assign_profile_processing_visitor.cpp index ae3d56c807..52eb83d185 100644 --- a/libs/image/processing/kis_assign_profile_processing_visitor.cpp +++ b/libs/image/processing/kis_assign_profile_processing_visitor.cpp @@ -1,88 +1,87 @@ /* * 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 "kis_assign_profile_processing_visitor.h" #include "kis_external_layer_iface.h" #include "kis_paint_device.h" #include "kis_transaction.h" #include "kis_undo_adapter.h" #include "kis_transform_mask.h" #include "lazybrush/kis_colorize_mask.h" #include #include "kis_projection_leaf.h" #include "kis_paint_layer.h" #include "kis_time_range.h" #include KisAssignProfileProcessingVisitor::KisAssignProfileProcessingVisitor(const KoColorSpace *srcColorSpace, const KoColorSpace *dstColorSpace) : m_srcColorSpace(srcColorSpace) , m_dstColorSpace(dstColorSpace) { } void KisAssignProfileProcessingVisitor::visitExternalLayer(KisExternalLayer *layer, KisUndoAdapter *undoAdapter) { undoAdapter->addCommand(layer->setProfile(m_dstColorSpace->profile())); } void KisAssignProfileProcessingVisitor::visitNodeWithPaintDevice(KisNode *node, KisUndoAdapter *undoAdapter) { if (!node->projectionLeaf()->isLayer()) return; if (*m_dstColorSpace == *node->colorSpace()) return; QSet paintDevices; paintDevices.insert(node->paintDevice()); paintDevices.insert(node->original()); paintDevices.insert(node->projection()); KUndo2Command *parentConversionCommand = new KUndo2Command(); Q_FOREACH (KisPaintDeviceSP dev, paintDevices) { if (dev->colorSpace()->colorModelId() == m_srcColorSpace->colorModelId()) { dev->setProfile(m_dstColorSpace->profile(), parentConversionCommand); } } undoAdapter->addCommand(parentConversionCommand); node->invalidateFrames(KisTimeRange::infinite(0), node->extent()); } void KisAssignProfileProcessingVisitor::visit(KisTransformMask *mask, KisUndoAdapter *undoAdapter) { mask->threadSafeForceStaticImageUpdate(); KisSimpleProcessingVisitor::visit(mask, undoAdapter); } void KisAssignProfileProcessingVisitor::visitColorizeMask(KisColorizeMask *mask, KisUndoAdapter *undoAdapter) { if (mask->colorSpace()->colorModelId() == m_srcColorSpace->colorModelId()) { KUndo2Command *parentConversionCommand = new KUndo2Command(); mask->setProfile(m_dstColorSpace->profile(), parentConversionCommand); undoAdapter->addCommand(parentConversionCommand); mask->invalidateFrames(KisTimeRange::infinite(0), mask->extent()); } - KisSimpleProcessingVisitor::visit(mask, undoAdapter); } diff --git a/libs/image/tests/CMakeLists.txt b/libs/image/tests/CMakeLists.txt index 0fc7893122..a8091263ab 100644 --- a/libs/image/tests/CMakeLists.txt +++ b/libs/image/tests/CMakeLists.txt @@ -1,155 +1,195 @@ # 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) -include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() set(KisRandomGeneratorDemoSources kis_random_generator_demo.cpp kimageframe.cpp) ki18n_wrap_ui(KisRandomGeneratorDemoSources kis_random_generator_demo.ui) add_executable(KisRandomGeneratorDemo ${KisRandomGeneratorDemoSources}) target_link_libraries(KisRandomGeneratorDemo kritaimage) ecm_mark_as_test(KisRandomGeneratorDemo) ecm_add_tests( kis_base_node_test.cpp kis_fast_math_test.cpp kis_node_test.cpp kis_node_facade_test.cpp kis_fixed_paint_device_test.cpp kis_layer_test.cpp kis_effect_mask_test.cpp kis_iterator_test.cpp kis_painter_test.cpp - kis_selection_test.cpp kis_count_visitor_test.cpp kis_projection_test.cpp kis_properties_configuration_test.cpp kis_transaction_test.cpp kis_pixel_selection_test.cpp kis_group_layer_test.cpp kis_paint_layer_test.cpp kis_adjustment_layer_test.cpp kis_annotation_test.cpp kis_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_colorize_mask_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_perspective_transform_worker_test.cpp kis_cs_conversion_test.cpp - kis_processings_test.cpp kis_projection_leaf_test.cpp kis_histogram_test.cpp kis_onion_skin_compositor_test.cpp - kis_paint_device_test.cpp kis_queues_progress_updater_test.cpp kis_image_animation_interface_test.cpp kis_walkers_test.cpp - kis_async_merger_test.cpp kis_cage_transform_worker_test.cpp kis_random_generator_test.cpp kis_keyframing_test.cpp kis_filter_mask_test.cpp LINK_LIBRARIES kritaimage Qt5::Test NAME_PREFIX "libs-image-" ) -krita_add_broken_unit_tests( - kis_transform_mask_test.cpp - kis_layer_styles_test.cpp - kis_update_scheduler_test.cpp +include(KritaAddBrokenUnitTest) + +krita_add_broken_unit_test( kis_transform_mask_test.cpp + TEST_NAME kis_transform_mask_test.cpp + 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.cpp + 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.cpp + 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.cpp + 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.cpp + LINK_LIBRARIES kritaimage Qt5::Test + NAME_PREFIX "libs-image-" +) + +krita_add_broken_unit_test( kis_selection_test.cpp + TEST_NAME kis_selection_test.cpp + LINK_LIBRARIES kritaimage Qt5::Test + NAME_PREFIX "libs-image-" +) + +krita_add_broken_unit_test( kis_processings_test.cpp + TEST_NAME kis_processings_test.cpp + 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.cpp + LINK_LIBRARIES kritaimage Qt5::Test + NAME_PREFIX "libs-image-" +) +krita_add_broken_unit_test( kis_async_merger_test.cpp + TEST_NAME kis_async_merger_test.cpp LINK_LIBRARIES kritaimage Qt5::Test NAME_PREFIX "libs-image-" ) diff --git a/libs/image/tests/kis_layer_styles_test.cpp b/libs/image/tests/kis_layer_styles_test.cpp index c875ceaac8..3fa3159936 100644 --- a/libs/image/tests/kis_layer_styles_test.cpp +++ b/libs/image/tests/kis_layer_styles_test.cpp @@ -1,284 +1,284 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_styles_test.h" #include #include "kis_transaction.h" #include "testutil.h" #include #include "layerstyles/kis_layer_style_filter.h" #include "layerstyles/kis_layer_style_filter_environment.h" #include "layerstyles/kis_ls_drop_shadow_filter.h" #include "kis_psd_layer_style.h" #include "layerstyles/kis_multiple_projection.h" #include "layerstyles/KisLayerStyleKnockoutBlower.h" struct TestConfig { TestConfig() : distance(0), angle(0), spread(0), size(0), noise(0), knocks_out(false), keep_original(false) { } int distance; int angle; int spread; int size; int noise; int knocks_out; int opacity; bool keep_original; void writeProperties(KisPSDLayerStyleSP style) const { style->context()->keep_original = keep_original; style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setDistance(distance); style->dropShadow()->setSpread(spread); style->dropShadow()->setSize(size); style->dropShadow()->setNoise(noise); style->dropShadow()->setKnocksOut(knocks_out); style->dropShadow()->setOpacity(opacity); } QString genTestname(const QString &prefix) const { return QString("%1_d_%2_an_%3_sz_%4_spr_%5_nz_%6_ko_%7_keep_%8") .arg(prefix) .arg(distance) .arg(angle) .arg(size) .arg(spread) .arg(noise) .arg(knocks_out) .arg(keep_original); } }; void testDropShadowImpl(const TestConfig &config, const QVector &applyRects, const QString &testName, bool useSeparateDevices) { Q_UNUSED(useSeparateDevices); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QRect srcRect(50, 50, 100, 100); QRect dstRect(0, 0, 200, 200); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->fill(srcRect, KoColor(Qt::red, cs)); KisMultipleProjection projection; KisLsDropShadowFilter lsFilter; KisPSDLayerStyleSP style(new KisPSDLayerStyle()); config.writeProperties(style); TestUtil::MaskParent parent; KisLayerStyleFilterEnvironment env(parent.layer.data()); KisLayerStyleKnockoutBlower blower; Q_FOREACH (const QRect &rc, applyRects) { lsFilter.processDirectly(dev, &projection, &blower, rc, style, &env); } // drop shadow doesn't use global knockout QVERIFY(blower.isEmpty()); KisPaintDeviceSP dst = new KisPaintDevice(cs); projection.apply(dst, dstRect, &env); QImage resultImage = dst->convertToQImage(0, dstRect); TestUtil::checkQImage(resultImage, "layer_styles_test", "common", config.genTestname(testName)); } void KisLayerStylesTest::testLayerStylesFull() { TestConfig c; c.distance = 20; c.angle = 135; c.spread = 50; c.size = 10; c.noise = 30; c.knocks_out = false; c.opacity = 50; c.keep_original = false; testDropShadowImpl(c, QVector() << QRect(0,0,200,200), "full", false); } void KisLayerStylesTest::testLayerStylesPartial() { QVector rects; for (int y = 0; y < 200; y += 50) { for (int x = 0; x < 200; x += 50) { rects << QRect(x, y, 50, 50); } } TestConfig c; c.distance = 20; c.angle = 135; c.spread = 50; c.size = 10; c.noise = 30; c.knocks_out = false; c.opacity = 50; c.keep_original = false; testDropShadowImpl(c, rects, "partial", true); } void KisLayerStylesTest::testLayerStylesPartialVary() { QVector rects; for (int y = 0; y < 200; y += 50) { for (int x = 0; x < 200; x += 50) { rects << QRect(x, y, 50, 50); } } TestConfig c; c.distance = 20; c.angle = 135; c.spread = 50; c.size = 10; c.noise = 30; c.knocks_out = false; c.opacity = 50; c.keep_original = true; testDropShadowImpl(c, rects, "partial", true); c.noise = 90; testDropShadowImpl(c, rects, "partial", true); c.noise = 0; testDropShadowImpl(c, rects, "partial", true); c.noise = 10; testDropShadowImpl(c, rects, "partial", true); c.angle = 90; testDropShadowImpl(c, rects, "partial", true); c.angle = 45; testDropShadowImpl(c, rects, "partial", true); c.knocks_out = true; testDropShadowImpl(c, rects, "partial", true); c.spread = 90; testDropShadowImpl(c, rects, "partial", true); } void testDropShadowNeedChangeRects(int distance, int noise, int size, int spread, const QRect &applyRect, const QRect &needRect, const QRect &changeRect) { TestConfig c; c.distance = distance; c.spread = spread; c.size = size; c.noise = noise; c.angle = 90; c.knocks_out = false; c.opacity = 50; KisLsDropShadowFilter lsFilter; KisPSDLayerStyleSP style(new KisPSDLayerStyle()); c.writeProperties(style); TestUtil::MaskParent parent; KisLayerStyleFilterEnvironment env(parent.layer.data()); QCOMPARE(lsFilter.neededRect(applyRect, style, &env), needRect); QCOMPARE(lsFilter.changedRect(applyRect, style, &env), changeRect); } void KisLayerStylesTest::testLayerStylesRects() { QRect applyRect; QRect needRect; QRect changeRect; applyRect = QRect(10,10,10,10); needRect = applyRect; changeRect = applyRect; testDropShadowNeedChangeRects(0, 0, 0, 0, applyRect, needRect, changeRect); applyRect = QRect(10,10,10,10); needRect = QRect(10,0,10,20); changeRect = QRect(10,10,10,20); testDropShadowNeedChangeRects(10, 0, 0, 0, applyRect, needRect, changeRect); applyRect = QRect(10,10,10,10); needRect = QRect(2,2,26,26); changeRect = QRect(2,2,26,26); testDropShadowNeedChangeRects(0, 30, 0, 0, applyRect, needRect, changeRect); applyRect = QRect(10,10,10,10); needRect = QRect(-2,-2,34,34); changeRect = QRect(-2,-2,34,34); testDropShadowNeedChangeRects(0, 0, 10, 0, applyRect, needRect, changeRect); applyRect = QRect(10,10,10,10); needRect = QRect(-2,-2,34,34); changeRect = QRect(-2,-2,34,34); testDropShadowNeedChangeRects(0, 0, 10, 50, applyRect, needRect, changeRect); applyRect = QRect(10,10,10,10); needRect = QRect(-2,-2,34,34); changeRect = QRect(-2,-2,34,34); testDropShadowNeedChangeRects(0, 0, 10, 75, applyRect, needRect, changeRect); } -QTEST_MAIN(KisLayerStylesTest) +KISTEST_MAIN(KisLayerStylesTest) diff --git a/libs/image/tiles3/kis_lockless_stack.h b/libs/image/tiles3/kis_lockless_stack.h index 6145af836a..2ec0cbc947 100644 --- a/libs/image/tiles3/kis_lockless_stack.h +++ b/libs/image/tiles3/kis_lockless_stack.h @@ -1,235 +1,235 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * References: * * Maged M. Michael, Safe memory reclamation for dynamic * lock-free objects using atomic reads and writes, * Proceedings of the twenty-first annual symposium on * Principles of distributed computing, July 21-24, 2002, * Monterey, California * * * Idea of m_deleteBlockers is taken from Andrey Gulin's blog - * http://users.livejournal.com/_foreseer/34284.html + * https://users.livejournal.com/-foreseer/34284.html * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_LOCKLESS_STACK_H #define __KIS_LOCKLESS_STACK_H #include template class KisLocklessStack { private: struct Node { Node *next; T data; }; public: KisLocklessStack() { } ~KisLocklessStack() { freeList(m_top.fetchAndStoreOrdered(0)); freeList(m_freeNodes.fetchAndStoreOrdered(0)); } void push(T data) { Node *newNode = new Node(); newNode->data = data; Node *top; do { top = m_top; newNode->next = top; } while (!m_top.testAndSetOrdered(top, newNode)); m_numNodes.ref(); } bool pop(T &value) { bool result = false; m_deleteBlockers.ref(); while(1) { Node *top = (Node*) m_top; if(!top) break; // This is safe as we ref'ed m_deleteBlockers Node *next = top->next; if(m_top.testAndSetOrdered(top, next)) { m_numNodes.deref(); result = true; value = top->data; /** * Test if we are the only delete blocker left * (it means that we are the only owner of 'top') * If there is someone else in "delete-blocked section", * then just add the struct to the list of free nodes. */ if (m_deleteBlockers == 1) { cleanUpNodes(); delete top; } else { releaseNode(top); } break; } } m_deleteBlockers.deref(); return result; } void clear() { // a fast-path without write ops if(!m_top) return; m_deleteBlockers.ref(); Node *top = m_top.fetchAndStoreOrdered(0); int removedChunkSize = 0; Node *tmp = top; while(tmp) { removedChunkSize++; tmp = tmp->next; } m_numNodes.fetchAndAddOrdered(-removedChunkSize); while(top) { Node *next = top->next; if (m_deleteBlockers == 1) { /** * We are the only owner of top contents. * So we can delete it freely. */ cleanUpNodes(); freeList(top); next = 0; } else { releaseNode(top); } top = next; } m_deleteBlockers.deref(); } void mergeFrom(KisLocklessStack &other) { Node *otherTop = other.m_top.fetchAndStoreOrdered(0); if (!otherTop) return; int removedChunkSize = 1; Node *last = otherTop; while(last->next) { removedChunkSize++; last = last->next; } other.m_numNodes.fetchAndAddOrdered(-removedChunkSize); Node *top; do { top = m_top; last->next = top; } while (!m_top.testAndSetOrdered(top, otherTop)); m_numNodes.fetchAndAddOrdered(removedChunkSize); } /** * This is impossible to measure the size of the stack * in highly concurrent environment. So we return approximate * value! Do not rely on this value much! */ qint32 size() const { return m_numNodes; } bool isEmpty() const { return !m_numNodes; } private: inline void releaseNode(Node *node) { Node *top; do { top = m_freeNodes; node->next = top; } while (!m_freeNodes.testAndSetOrdered(top, node)); } inline void cleanUpNodes() { Node *cleanChain = m_freeNodes.fetchAndStoreOrdered(0); if (!cleanChain) return; /** * If we are the only users of the objects is cleanChain, * then just free it. Otherwise, push them back into the * recycling list and keep them there till another * chance comes. */ if (m_deleteBlockers == 1) { freeList(cleanChain); } else { Node *last = cleanChain; while (last->next) last = last->next; Node *freeTop; do { freeTop = m_freeNodes; last->next = freeTop; } while (!m_freeNodes.testAndSetOrdered(freeTop, cleanChain)); } } inline void freeList(Node *first) { Node *next; while (first) { next = first->next; delete first; first = next; } } private: Q_DISABLE_COPY(KisLocklessStack) QAtomicPointer m_top; QAtomicPointer m_freeNodes; QAtomicInt m_deleteBlockers; QAtomicInt m_numNodes; }; #endif /* __KIS_LOCKLESS_STACK_H */ diff --git a/libs/libkis/Canvas.cpp b/libs/libkis/Canvas.cpp index 0bbddf9979..06c0b0b60d 100644 --- a/libs/libkis/Canvas.cpp +++ b/libs/libkis/Canvas.cpp @@ -1,142 +1,142 @@ /* * 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 "Canvas.h" #include #include #include #include #include #include #include #include struct Canvas::Private { Private() {} KisCanvas2 *canvas; }; Canvas::Canvas(KoCanvasBase *canvas, QObject *parent) : QObject(parent) , d(new Private) { d->canvas = static_cast(canvas); } Canvas::~Canvas() { delete d; } bool Canvas::operator==(const Canvas &other) const { return (d->canvas == other.d->canvas); } bool Canvas::operator!=(const Canvas &other) const { return !(operator==(other)); } qreal Canvas::zoomLevel() const { if (!d->canvas) return 1.0; return d->canvas->imageView()->zoomManager()->zoom(); } void Canvas::setZoomLevel(qreal value) { if (!d->canvas) return; d->canvas->imageView()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, value); } void Canvas::resetZoom() { if (!d->canvas) return; d->canvas->imageView()->zoomManager()->zoomTo100(); } void Canvas::resetRotation() { if (!d->canvas) return; d->canvas->imageView()->canvasController()->resetCanvasRotation(); } qreal Canvas::rotation() const { if (!d->canvas) return 0; return d->canvas->imageView()->canvasController()->rotation(); } void Canvas::setRotation(qreal angle) { if (!d->canvas) return; - d->canvas->imageView()->canvasController()->rotateCanvas(angle); + d->canvas->imageView()->canvasController()->rotateCanvas(angle - rotation()); } bool Canvas::mirror() const { if (!d->canvas) return false; return d->canvas->imageView()->canvasIsMirrored(); } void Canvas::setMirror(bool value) { if (!d->canvas) return; d->canvas->imageView()->canvasController()->mirrorCanvas(value); } View *Canvas::view() const { if (!d->canvas) return 0; View *view = new View(d->canvas->imageView()); return view; } KisDisplayColorConverter *Canvas::displayColorConverter() const { if (!d->canvas) return 0; return d->canvas->displayColorConverter(); } bool Canvas::wrapAroundMode() const { if (!d->canvas) return false; return d->canvas->imageView()->canvasController()->wrapAroundMode(); } void Canvas::setWrapAroundMode(bool enable) { if (!d->canvas) return; d->canvas->imageView()->canvasController()->slotToggleWrapAroundMode(enable); } bool Canvas::levelOfDetailMode() const { if (!d->canvas) return false; return d->canvas->imageView()->canvasController()->levelOfDetailMode(); } void Canvas::setLevelOfDetailMode(bool enable) { if (!d->canvas) return; return d->canvas->imageView()->canvasController()->slotToggleLevelOfDetailMode(enable); } diff --git a/libs/libkis/FillLayer.h b/libs/libkis/FillLayer.h index 07740b2a08..209b682c17 100644 --- a/libs/libkis/FillLayer.h +++ b/libs/libkis/FillLayer.h @@ -1,100 +1,100 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef LIBKIS_FILLLAYER_H #define LIBKIS_FILLLAYER_H #include #include "Node.h" #include #include #include #include "kritalibkis_export.h" #include "libkis.h" /** * @brief The FillLayer class * A fill layer is much like a filter layer in that it takes a name * and filter. It however specializes in filters that fill the whole canvas, * such as a pattern or full color fill. */ class KRITALIBKIS_EXPORT FillLayer : public Node { Q_OBJECT Q_DISABLE_COPY(FillLayer) public: /** * @brief FillLayer Create a new fill layer with the given generator plugin * @param image the image this fill layer will belong to * @param name "pattern" or "color" * @param filterConfig a configuration object appropriate to the given generator plugin * * For a "pattern" fill layer, the InfoObject can contain a single "pattern" parameter with * the name of a pattern as known to the resource system: "pattern" = "Cross01.pat". * * For a "color" fill layer, the InfoObject can contain a single "color" parameter with - * a QColor, a string that QColor can parse (see http://doc.qt.io/qt-5/qcolor.html#setNamedColor) + * a QColor, a string that QColor can parse (see https://doc.qt.io/qt-5/qcolor.html#setNamedColor) * or an XML description of the color, which can be derived from a @see ManagedColor. * * @param selection a selection object, can be empty * @param parent */ explicit FillLayer(KisImageSP image, QString name, KisFilterConfigurationSP filterConfig, Selection &selection, QObject *parent = 0); explicit FillLayer(KisGeneratorLayerSP layer, QObject *parent = 0); ~FillLayer() override; public Q_SLOTS: /** * @brief type Krita has several types of nodes, split in layers and masks. Group * layers can contain other layers, any layer can contain masks. * * @return The type of the node. Valid types are: *
    *
  • paintlayer *
  • grouplayer *
  • filelayer *
  • filterlayer *
  • filllayer *
  • clonelayer *
  • vectorlayer *
  • transparencymask *
  • filtermask *
  • transformmask *
  • selectionmask *
  • colorizemask *
* * If the Node object isn't wrapping a valid Krita layer or mask object, and * empty string is returned. */ virtual QString type() const override; /** * @brief setGenerator set the given generator for this fill layer * @param generatorName "pattern" or "color" * @param filterConfig a configuration object appropriate to the given generator plugin * @return true if the generator was correctly created and set on the layer */ bool setGenerator(const QString &generatorName, InfoObject *filterConfig); QString generatorName(); InfoObject *filterConfig(); }; #endif // LIBKIS_FILLLAYER_H diff --git a/libs/libkis/InfoObject.h b/libs/libkis/InfoObject.h index ee8c4b9ff7..b3beb91ef0 100644 --- a/libs/libkis/InfoObject.h +++ b/libs/libkis/InfoObject.h @@ -1,88 +1,88 @@ /* * 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_INFOOBJECT_H #define LIBKIS_INFOOBJECT_H #include #include #include "kritalibkis_export.h" #include "libkis.h" /** * InfoObject wrap a properties map. These maps can be used to set the * configuration for filters. */ class KRITALIBKIS_EXPORT InfoObject : public QObject { Q_OBJECT public: InfoObject(KisPropertiesConfigurationSP configuration); /** * Create a new, empty InfoObject. */ explicit InfoObject(QObject *parent = 0); ~InfoObject() override; bool operator==(const InfoObject &other) const; bool operator!=(const InfoObject &other) const; /** * Return all properties this InfoObject manages. */ QMap properties() const; /** * Add all properties in the @p propertyMap to this InfoObject */ void setProperties(QMap propertyMap); public Q_SLOTS: /** * set the property identified by @p key to @p value * * If you want create a property that represents a color, you can use a QColor - * or hex string, as defined in http://doc.qt.io/qt-5/qcolor.html#setNamedColor. + * or hex string, as defined in https://doc.qt.io/qt-5/qcolor.html#setNamedColor. * */ void setProperty(const QString &key, QVariant value); /** * return the value for the property identified by key, or None if there is no such key. */ QVariant property(const QString &key); private: friend class Filter; friend class Document; friend class Node; /** * @brief configuration gives access to the internal configuration object. Must * be used used internally in libkis * @return the internal configuration object. */ KisPropertiesConfigurationSP configuration() const; struct Private; Private *d; }; #endif // LIBKIS_INFOOBJECT_H diff --git a/libs/libkis/ManagedColor.cpp b/libs/libkis/ManagedColor.cpp index 75199bdbcd..3d905a1054 100644 --- a/libs/libkis/ManagedColor.cpp +++ b/libs/libkis/ManagedColor.cpp @@ -1,174 +1,191 @@ /* * Copyright (C) 2017 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ManagedColor.h" #include #include #include #include #include #include #include #include #include #include struct ManagedColor::Private { KoColor color; }; ManagedColor::ManagedColor(QObject *parent) : QObject(parent) , d(new Private()) { // Default black rgb color } ManagedColor::ManagedColor(const QString &colorModel, const QString &colorDepth, const QString &colorProfile, QObject *parent) : QObject(parent) , d(new Private()) { const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile); if (colorSpace) { d->color = KoColor(colorSpace); } } ManagedColor::ManagedColor(KoColor color, QObject *parent) : QObject(parent) , d(new Private()) { d->color = color; } ManagedColor::~ManagedColor() { } bool ManagedColor::operator==(const ManagedColor &other) const { return d->color == other.d->color; } + QColor ManagedColor::colorForCanvas(Canvas *canvas) const { QColor c = QColor(0,0,0); if (canvas && canvas->displayColorConverter() && canvas->displayColorConverter()->displayRendererInterface()) { KoColorDisplayRendererInterface *converter = canvas->displayColorConverter()->displayRendererInterface(); if (converter) { c = converter->toQColor(d->color); } else { c = KoDumbColorDisplayRenderer::instance()->toQColor(d->color); } } else { c = KoDumbColorDisplayRenderer::instance()->toQColor(d->color); } return c; } +ManagedColor *ManagedColor::fromQColor(const QColor &qcolor, Canvas *canvas) +{ + KoColor c; + if (canvas && canvas->displayColorConverter() && canvas->displayColorConverter()->displayRendererInterface()) { + KoColorDisplayRendererInterface *converter = canvas->displayColorConverter()->displayRendererInterface(); + if (converter) { + c = converter->approximateFromRenderedQColor(qcolor); + } else { + c = KoDumbColorDisplayRenderer::instance()->approximateFromRenderedQColor(qcolor); + } + } else { + c = KoDumbColorDisplayRenderer::instance()->approximateFromRenderedQColor(qcolor); + } + return new ManagedColor(c); +} + QString ManagedColor::colorDepth() const { return d->color.colorSpace()->colorDepthId().id(); } QString ManagedColor::colorModel() const { return d->color.colorSpace()->colorModelId().id(); } QString ManagedColor::colorProfile() const { return d->color.colorSpace()->profile()->name(); } bool ManagedColor::setColorProfile(const QString &colorProfile) { const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(colorProfile); if (!profile) return false; d->color.setProfile(profile); return true; } bool ManagedColor::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile) { const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile); if (!colorSpace) return false; d->color.convertTo(colorSpace); return true; } QVector ManagedColor::components() const { QVector values(d->color.colorSpace()->channelCount()); d->color.colorSpace()->normalisedChannelsValue(d->color.data(), values); return values; } QVector ManagedColor::componentsOrdered() const { QVector valuesUnsorted = components(); QVector values(d->color.colorSpace()->channelCount()); for (int i=0; icolor.colorSpace()->channels()); values[location] = valuesUnsorted[i]; } return values; } void ManagedColor::setComponents(const QVector &values) { d->color.colorSpace()->fromNormalisedChannelsValue(d->color.data(), values); } QString ManagedColor::toXML() const { QDomDocument doc; QDomElement root = doc.createElement("Color"); root.setAttribute("bitdepth", colorDepth()); doc.appendChild(root); d->color.toXML(doc, root); return doc.toString(); } void ManagedColor::fromXML(const QString &xml) { QDomDocument doc; doc.setContent(xml); QDomElement e = doc.documentElement(); QDomElement c = e.firstChildElement("Color"); KoColor kc; if (!c.isNull()) { QString colorDepthId = c.attribute("bitdepth", Integer8BitsColorDepthID.id()); d->color = KoColor::fromXML(c, colorDepthId); } } QString ManagedColor::toQString() { return KoColor::toQString(d->color); } KoColor ManagedColor::color() const { return d->color; } diff --git a/libs/libkis/ManagedColor.h b/libs/libkis/ManagedColor.h index 5b5837ee17..a8376960f0 100644 --- a/libs/libkis/ManagedColor.h +++ b/libs/libkis/ManagedColor.h @@ -1,211 +1,220 @@ /* * Copyright (C) 2017 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MANAGEDCOLOR_H #define MANAGEDCOLOR_H #include #include #include #include "kritalibkis_export.h" #include "libkis.h" class KoColor; /** * @brief The ManagedColor class is a class to handle colors that are color managed. * A managed color is a color of which we know the model(RGB, LAB, CMYK, etc), the bitdepth and * the specific properties of its colorspace, such as the whitepoint, chromacities, trc, etc, as represented * by the color profile. * * Krita has two color management systems. LCMS and OCIO. * LCMS is the one handling the ICC profile stuff, and the major one handling that ManagedColor deals with. * OCIO support is only in the display of the colors. ManagedColor has some support for it in colorForCanvas() * * All colors in Krita are color managed. QColors are understood as RGB-type colors in the sRGB space. * * We recommend you make a color like this: * * @code * colorYellow = ManagedColor("RGBA", "U8", "") * QVector yellowComponents = colorYellow.components() * yellowComponents[0] = 1.0 * yellowComponents[1] = 1.0 * yellowComponents[2] = 0 * yellowComponents[3] = 1.0 * * colorYellow.setComponents(yellowComponents) * QColor yellow = colorYellow.colorForCanvas(canvas) * @endcode */ class KRITALIBKIS_EXPORT ManagedColor : public QObject { Q_OBJECT public: /** * @brief ManagedColor * Create a ManagedColor that is black and transparent. */ explicit ManagedColor(QObject *parent = 0); /** * @brief ManagedColor create a managed color with the given color space properties. * @see setColorModel() for more details. */ ManagedColor(const QString &colorModel, const QString &colorDepth, const QString &colorProfile, QObject *parent = 0); ManagedColor(KoColor color, QObject *parent = 0); ~ManagedColor() override; bool operator==(const ManagedColor &other) const; /** * @brief colorForCanvas * @param canvas the canvas whose color management you'd like to use. In Krita, different views have * separate canvasses, and these can have different OCIO configurations active. * @return the QColor as it would be displaying on the canvas. This result can be used to draw widgets with * the correct configuration applied. */ QColor colorForCanvas(Canvas *canvas) const; + + /** + * @brief fromQColor is the (approximate) reverse of colorForCanvas() + * @param qcolor the QColor to convert to a KoColor. + * @param canvas the canvas whose color management you'd like to use. + * @return the approximated ManagedColor, to use for canvas resources. + */ + static ManagedColor *fromQColor(const QColor &qcolor, Canvas *canvas = 0); + /** * 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 the color depth. */ QString colorDepth() const; /** * @brief colorModel retrieve the current color model of this document: *
    *
  • 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
  • *
* @return the internal color model string. */ QString colorModel() const; /** * @return the name of the current color profile */ QString colorProfile() const; /** * @brief setColorProfile set the color profile of the image to the given profile. The profile has to * be registered with krita and be compatible with the current color model and depth; the image data * is not converted. * @param colorProfile * @return false if the colorProfile name does not correspond to to a registered profile or if assigning * the profile failed. */ bool setColorProfile(const QString &colorProfile); /** * @brief setColorSpace convert the nodes and the image to the given colorspace. The conversion is * done with Perceptual as intent, High Quality and No LCMS Optimizations as flags and no blackpoint * compensation. * * @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 colorProfile a valid color profile for this color model and color depth combination. * @return false the combination of these arguments does not correspond to a colorspace. */ bool setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile); /** * @brief components * @return a QVector containing the channel/components of this color normalized. This includes the alphachannel. */ QVector components() const; /** * @brief componentsOrdered() * @return same as Components, except the values are ordered to the display. */ QVector componentsOrdered() const; /** * @brief setComponents * Set the channel/components with normalized values. For integer colorspace, this obviously means the limit * is between 0.0-1.0, but for floating point colorspaces, 2.4 or 103.5 are still meaningful (if bright) values. * @param values the QVector containing the new channel/component values. These should be normalized. */ void setComponents(const QVector &values); /** * Serialize this color following Create's swatch color specification available - * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format + * at https://web.archive.org/web/20110826002520/http://create.freedesktop.org/wiki/Swatches_-_colour_file_format/Draft */ QString toXML() const; /** * Unserialize a color following Create's swatch color specification available - * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format + * at https://web.archive.org/web/20110826002520/http://create.freedesktop.org/wiki/Swatches_-_colour_file_format/Draft * * @param xml an XML color * * @return the unserialized color, or an empty color object if the function failed * to unserialize the color */ void fromXML(const QString &xml); /** * @brief toQString create a user-visible string of the channel names and the channel values * @return a string that can be used to display the values of this color to the user. */ QString toQString(); private: friend class View; friend class PaletteView; friend class Swatch; KoColor color() const; struct Private; const QScopedPointer d; }; #endif // MANAGEDCOLOR_H diff --git a/libs/odf/KoPageFormat.cpp b/libs/odf/KoPageFormat.cpp index ee6be9342b..686e79c34f 100644 --- a/libs/odf/KoPageFormat.cpp +++ b/libs/odf/KoPageFormat.cpp @@ -1,181 +1,181 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright 2002, 2003 David Faure Copyright 2003 Nicolas GOUTTE Copyright 2007 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPageFormat.h" #include #include // paper formats ( mm ) #define PG_A3_WIDTH 297.0 #define PG_A3_HEIGHT 420.0 #define PG_A4_WIDTH 210.0 #define PG_A4_HEIGHT 297.0 #define PG_A5_WIDTH 148.0 #define PG_A5_HEIGHT 210.0 #define PG_B5_WIDTH 182.0 #define PG_B5_HEIGHT 257.0 #define PG_US_LETTER_WIDTH 216.0 #define PG_US_LETTER_HEIGHT 279.0 #define PG_US_LEGAL_WIDTH 216.0 #define PG_US_LEGAL_HEIGHT 356.0 #define PG_US_EXECUTIVE_WIDTH 191.0 #define PG_US_EXECUTIVE_HEIGHT 254.0 struct PageFormatInfo { KoPageFormat::Format format; QPrinter::PageSize qprinter; const char* shortName; // Short name const char* descriptiveName; // Full name, which will be translated qreal width; // in mm qreal height; // in mm }; // NOTES: // - the width and height of non-ISO formats are rounded -// http://en.wikipedia.org/wiki/Paper_size can help +// https://en.wikipedia.org/wiki/Paper_size can help // - the comments "should be..." indicates the exact values if the inch sizes would be multiplied by 25.4 mm/inch const PageFormatInfo pageFormatInfo[] = { { KoPageFormat::IsoA3Size, QPrinter::A3, "A3", I18N_NOOP2("Page size", "ISO A3"), 297.0, 420.0 }, { KoPageFormat::IsoA4Size, QPrinter::A4, "A4", I18N_NOOP2("Page size", "ISO A4"), 210.0, 297.0 }, { KoPageFormat::IsoA5Size, QPrinter::A5, "A5", I18N_NOOP2("Page size", "ISO A5"), 148.0, 210.0 }, { KoPageFormat::UsLetterSize, QPrinter::Letter, "Letter", I18N_NOOP2("Page size", "US Letter"), 215.9, 279.4 }, { KoPageFormat::UsLegalSize, QPrinter::Legal, "Legal", I18N_NOOP2("Page size", "US Legal"), 215.9, 355.6 }, { KoPageFormat::ScreenSize, QPrinter::A4, "Screen", I18N_NOOP2("Page size", "Screen"), PG_A4_HEIGHT, PG_A4_WIDTH }, // Custom, so fall back to A4 { KoPageFormat::CustomSize, QPrinter::A4, "Custom", I18N_NOOP2("Page size", "Custom"), PG_A4_WIDTH, PG_A4_HEIGHT }, // Custom, so fall back to A4 { KoPageFormat::IsoB5Size, QPrinter::B5, "B5", I18N_NOOP2("Page size", "ISO B5"), 182.0, 257.0 }, { KoPageFormat::UsExecutiveSize, QPrinter::Executive, "Executive", I18N_NOOP2("Page size", "US Executive"), 191.0, 254.0 }, // should be 190.5 mm x 254.0 mm { KoPageFormat::IsoA0Size, QPrinter::A0, "A0", I18N_NOOP2("Page size", "ISO A0"), 841.0, 1189.0 }, { KoPageFormat::IsoA1Size, QPrinter::A1, "A1", I18N_NOOP2("Page size", "ISO A1"), 594.0, 841.0 }, { KoPageFormat::IsoA2Size, QPrinter::A2, "A2", I18N_NOOP2("Page size", "ISO A2"), 420.0, 594.0 }, { KoPageFormat::IsoA6Size, QPrinter::A6, "A6", I18N_NOOP2("Page size", "ISO A6"), 105.0, 148.0 }, { KoPageFormat::IsoA7Size, QPrinter::A7, "A7", I18N_NOOP2("Page size", "ISO A7"), 74.0, 105.0 }, { KoPageFormat::IsoA8Size, QPrinter::A8, "A8", I18N_NOOP2("Page size", "ISO A8"), 52.0, 74.0 }, { KoPageFormat::IsoA9Size, QPrinter::A9, "A9", I18N_NOOP2("Page size", "ISO A9"), 37.0, 52.0 }, { KoPageFormat::IsoB0Size, QPrinter::B0, "B0", I18N_NOOP2("Page size", "ISO B0"), 1030.0, 1456.0 }, { KoPageFormat::IsoB1Size, QPrinter::B1, "B1", I18N_NOOP2("Page size", "ISO B1"), 728.0, 1030.0 }, { KoPageFormat::IsoB10Size, QPrinter::B10, "B10", I18N_NOOP2("Page size", "ISO B10"), 32.0, 45.0 }, { KoPageFormat::IsoB2Size, QPrinter::B2, "B2", I18N_NOOP2("Page size", "ISO B2"), 515.0, 728.0 }, { KoPageFormat::IsoB3Size, QPrinter::B3, "B3", I18N_NOOP2("Page size", "ISO B3"), 364.0, 515.0 }, { KoPageFormat::IsoB4Size, QPrinter::B4, "B4", I18N_NOOP2("Page size", "ISO B4"), 257.0, 364.0 }, { KoPageFormat::IsoB6Size, QPrinter::B6, "B6", I18N_NOOP2("Page size", "ISO B6"), 128.0, 182.0 }, { KoPageFormat::IsoC5Size, QPrinter::C5E, "C5", I18N_NOOP2("Page size", "ISO C5"), 163.0, 229.0 }, // Some sources tells: 162 mm x 228 mm { KoPageFormat::UsComm10Size, QPrinter::Comm10E, "Comm10", I18N_NOOP2("Page size", "US Common 10"), 105.0, 241.0 }, // should be 104.775 mm x 241.3 mm { KoPageFormat::IsoDLSize, QPrinter::DLE, "DL", I18N_NOOP2("Page size", "ISO DL"), 110.0, 220.0 }, { KoPageFormat::UsFolioSize, QPrinter::Folio, "Folio", I18N_NOOP2("Page size", "US Folio"), 210.0, 330.0 }, // should be 209.54 mm x 330.2 mm { KoPageFormat::UsLedgerSize, QPrinter::Ledger, "Ledger", I18N_NOOP2("Page size", "US Ledger"), 432.0, 279.0 }, // should be 431.8 mm x 297.4 mm { KoPageFormat::UsTabloidSize, QPrinter::Tabloid, "Tabloid", I18N_NOOP2("Page size", "US Tabloid"), 279.0, 432.0 }, // should be 297.4 mm x 431.8 mm { KoPageFormat::Invalid, QPrinter::Custom, "", "", -1, -1 } }; QPrinter::PageSize KoPageFormat::printerPageSize(KoPageFormat::Format format) { if (format == ScreenSize) { warnOdf << "You use the page layout SCREEN. Printing in ISO A4 Landscape."; return QPrinter::A4; } if (format == CustomSize) { warnOdf << "The used page layout (Custom) is not supported by KQPrinter. Printing in A4."; return QPrinter::A4; } return pageFormatInfo[format].qprinter; } qreal KoPageFormat::width(Format format, Orientation orientation) { if (orientation == Landscape) return height(format, Portrait); return pageFormatInfo[format].width; } qreal KoPageFormat::height(Format format, Orientation orientation) { if (orientation == Landscape) return width(format, Portrait); return pageFormatInfo[format].height; } KoPageFormat::Format KoPageFormat::guessFormat(qreal width, qreal height) { for (int i = 0; pageFormatInfo[i].format != KoPageFormat::Invalid ;i++) { // We have some tolerance. 1pt is a third of a mm, this is // barely noticeable for a page size. if (qAbs(width - pageFormatInfo[i].width) < 1.0 && qAbs(height - pageFormatInfo[i].height) < 1.0) return pageFormatInfo[i].format; } return CustomSize; } QString KoPageFormat::formatString(Format format) { return QString::fromLatin1(pageFormatInfo[format].shortName); } KoPageFormat::Format KoPageFormat::formatFromString(const QString & string) { for (int i = 0; pageFormatInfo[i].format != KoPageFormat::Invalid ;i++) { if (string == QString::fromLatin1(pageFormatInfo[i].shortName)) return pageFormatInfo[i].format; } // We do not know the format name, so we have a custom format return CustomSize; } KoPageFormat::Format KoPageFormat::defaultFormat() { int qprinter; if (QLocale().measurementSystem() == QLocale::ImperialSystem) { qprinter = static_cast(QPageSize::Letter); } else { qprinter = static_cast(QPageSize::A4); } for (int i = 0; pageFormatInfo[i].format != KoPageFormat::Invalid ;i++) { if (pageFormatInfo[i].qprinter == qprinter) return static_cast(i); } return IsoA4Size; } QString KoPageFormat::name(Format format) { return i18nc("Page size", pageFormatInfo[format].descriptiveName); } QStringList KoPageFormat::localizedPageFormatNames() { QStringList lst; for (int i = 0; pageFormatInfo[i].format != KoPageFormat::Invalid ;i++) { lst << i18nc("Page size", pageFormatInfo[i].descriptiveName); } return lst; } QStringList KoPageFormat::pageFormatNames() { QStringList lst; for (int i = 0; pageFormatInfo[i].format != KoPageFormat::Invalid ;i++) { lst << pageFormatInfo[i].shortName; } return lst; } diff --git a/libs/pigment/KoCmykColorSpaceTraits.h b/libs/pigment/KoCmykColorSpaceTraits.h index a81385468f..ec1f9dbb7b 100644 --- a/libs/pigment/KoCmykColorSpaceTraits.h +++ b/libs/pigment/KoCmykColorSpaceTraits.h @@ -1,238 +1,238 @@ /* * Copyright (c) 2006-2007 Cyrille Berger - * Copyright (c) 2016 L. E. Segovia + * Copyright (c) 2016 L. E. Segovia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KO_CMYK_COLORSPACE_TRAITS_H_ #define _KO_CMYK_COLORSPACE_TRAITS_H_ #include #include "KoColorSpaceConstants.h" #include "KoColorSpaceMaths.h" #include "DebugPigment.h" /** * Base class for CMYK traits, it provides some convenient functions to * access CMYK channels through an explicit API. */ template struct KoCmykTraits : public KoColorSpaceTrait<_channels_type_, 5, 4> { typedef _channels_type_ channels_type; typedef KoColorSpaceTrait<_channels_type_, 5, 4> parent; static const qint32 c_pos = 0; static const qint32 m_pos = 1; static const qint32 y_pos = 2; static const qint32 k_pos = 3; /** * An CMYK pixel */ struct Pixel { channels_type cyan; channels_type magenta; channels_type yellow; channels_type black; channels_type alpha; }; /// @return the Cyan component inline static channels_type C(quint8* data) { channels_type* d = parent::nativeArray(data); return d[c_pos]; } /// Set the Cyan component inline static void setC(quint8* data, channels_type nv) { channels_type* d = parent::nativeArray(data); d[c_pos] = nv; } /// @return the Magenta component inline static channels_type M(quint8* data) { channels_type* d = parent::nativeArray(data); return d[m_pos]; } /// Set the Magenta component inline static void setM(quint8* data, channels_type nv) { channels_type* d = parent::nativeArray(data); d[m_pos] = nv; } /// @return the Yellow component inline static channels_type Y(quint8* data) { channels_type* d = parent::nativeArray(data); return d[y_pos]; } /// Set the Yellow component inline static void setY(quint8* data, channels_type nv) { channels_type* d = parent::nativeArray(data); d[y_pos] = nv; } /// @return the Key component inline static channels_type k(quint8* data) { channels_type* d = parent::nativeArray(data); return d[k_pos]; } /// Set the Key component inline static void setK(quint8* data, channels_type nv) { channels_type* d = parent::nativeArray(data); d[k_pos] = nv; } }; struct KoCmykU8Traits : public KoCmykTraits { }; struct KoCmykU16Traits : public KoCmykTraits { }; #include #ifdef HAVE_OPENEXR #include struct KoCmykF16Traits : public KoCmykTraits { static constexpr float MAX_CHANNEL_CMYK = 100; inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { return channelValueText(pixel, channelIndex); } inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() == (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { c = parent::nativeArray(pixel)[i]; channels[i] = (qreal)c; } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() == (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { float b = 0; switch(i) { case c_pos: case m_pos: case y_pos: case k_pos: b = qBound((float)0, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)MAX_CHANNEL_CMYK); break; default: b = qBound((float)KoColorSpaceMathsTraits::min, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)KoColorSpaceMathsTraits::max); break; } c = (channels_type)b; parent::nativeArray(pixel)[i] = c; } } }; #endif struct KoCmykF32Traits : public KoCmykTraits { static constexpr float MAX_CHANNEL_CMYK = 100; inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { return channelValueText(pixel, channelIndex); } inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() == (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { c = parent::nativeArray(pixel)[i]; channels[i] = (qreal)c; } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() == (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { float b = 0; switch(i) { case c_pos: case m_pos: case y_pos: case k_pos: b = qBound((float)0, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)MAX_CHANNEL_CMYK); break; default: b = qBound((float)KoColorSpaceMathsTraits::min, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)KoColorSpaceMathsTraits::max); break; } c = (channels_type)b; parent::nativeArray(pixel)[i] = c; } } }; struct KoCmykF64Traits : public KoCmykTraits { static constexpr double MAX_CHANNEL_CMYK = 100; inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { return channelValueText(pixel, channelIndex); } inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() == (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { c = parent::nativeArray(pixel)[i]; channels[i] = (qreal)c; } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() == (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { float b = 0; switch(i) { case c_pos: case m_pos: case y_pos: case k_pos: b = qBound((double)0, (double)KoColorSpaceMathsTraits::unitValue * values[i], (double)MAX_CHANNEL_CMYK); break; default: b = qBound((double)KoColorSpaceMathsTraits::min, (double)KoColorSpaceMathsTraits::unitValue * values[i], (double)KoColorSpaceMathsTraits::max); break; } c = (channels_type)b; parent::nativeArray(pixel)[i] = c; } } }; #endif diff --git a/libs/pigment/KoColor.h b/libs/pigment/KoColor.h index b231e484fc..cc8fd2d8f7 100644 --- a/libs/pigment/KoColor.h +++ b/libs/pigment/KoColor.h @@ -1,293 +1,293 @@ /* * Copyright (c) 2005 Boudewijn Rempt * Copyright (C) 2007 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCOLOR_H #define KOCOLOR_H #include #include #include #include "kritapigment_export.h" #include "KoColorConversionTransformation.h" #include "KoColorSpaceRegistry.h" #include "KoColorSpaceTraits.h" #include class QDomDocument; class QDomElement; class KoColorProfile; class KoColorSpace; /** * A KoColor describes a color in a certain colorspace. The color is stored in a buffer * that can be manipulated by the function of the color space. */ class KRITAPIGMENT_EXPORT KoColor : public boost::equality_comparable { public: /// Create an empty KoColor. It will be valid, but also black and transparent KoColor(); /// Create a null KoColor. It will be valid, but all channels will be set to 0 explicit KoColor(const KoColorSpace * colorSpace); /// Create a KoColor from a QColor. The QColor is immediately converted to native. The QColor /// is assumed to have the current monitor profile. KoColor(const QColor & color, const KoColorSpace * colorSpace); /// Create a KoColor using a native color strategy. The data is copied. KoColor(const quint8 * data, const KoColorSpace * colorSpace); /// Create a KoColor by converting src into another colorspace KoColor(const KoColor &src, const KoColorSpace * colorSpace); /// Copy constructor -- deep copies the colors. KoColor(const KoColor & rhs) { *this = rhs; } /** * assignment operator to copy the data from the param color into this one. * @param rhs the color we are going to copy * @return this color */ inline KoColor &operator=(const KoColor &rhs) { if (&rhs == this) { return *this; } m_colorSpace = rhs.m_colorSpace; m_size = rhs.m_size; memcpy(m_data, rhs.m_data, m_size); assertPermanentColorspace(); return *this; } bool operator==(const KoColor &other) const { if (*colorSpace() != *other.colorSpace()) { return false; } if (m_size != other.m_size) { return false; } return memcmp(m_data, other.m_data, m_size) == 0; } /// return the current colorSpace const KoColorSpace * colorSpace() const { return m_colorSpace; } /// return the current profile const KoColorProfile *profile() const; /// Convert this KoColor to the specified colorspace. If the specified colorspace is the /// same as the original colorspace, do nothing void convertTo(const KoColorSpace * cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); void convertTo(const KoColorSpace * cs); /// Copies this color and converts it to the specified colorspace. If the specified colorspace is the /// same as the original colorspace, just returns a copy KoColor convertedTo(const KoColorSpace * cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /// Copies this color and converts it to the specified colorspace. If the specified colorspace is the /// same as the original colorspace, just returns a copy KoColor convertedTo(const KoColorSpace * cs) const; /// assign new profile without converting pixel data void setProfile(const KoColorProfile *profile); /// Replace the existing color data, and colorspace with the specified data. /// The data is copied. void setColor(const quint8 * data, const KoColorSpace * colorSpace = 0); /// Convert the color from src and replace the value of the current color with the converted data. /// Don't convert the color if src and this have the same colorspace. void fromKoColor(const KoColor& src); /// a convenience method for the above. void toQColor(QColor *c) const; /// a convenience method for the above. QColor toQColor() const; /** * Convenient function to set the opacity of the color. */ void setOpacity(quint8 alpha); void setOpacity(qreal alpha); /** * Convenient function that return the opacity of the color */ quint8 opacityU8() const; qreal opacityF() const; /// Convenient function for converting from a QColor void fromQColor(const QColor& c); /** * @return the buffer associated with this color object to be used with the * transformation object created by the color space of this KoColor * or to copy to a different buffer from the same color space */ quint8 * data() { return m_data; } /** * @return the buffer associated with this color object to be used with the * transformation object created by the color space of this KoColor * or to copy to a different buffer from the same color space */ const quint8 * data() const { return m_data; } /** * Channelwise subtracts \p value from *this and stores the result in *this * * Throws a safe assert if the colorspaces of the two colors are different */ void subtract(const KoColor &value); /** * Channelwise subtracts \p value from a copy of *this and returns the result * * Throws a safe assert if the colorspaces of the two colors are different */ KoColor subtracted(const KoColor &value) const; /** * Channelwise adds \p value to *this and stores the result in *this * * Throws a safe assert if the colorspaces of the two colors are different */ void add(const KoColor &value); /** * Channelwise adds \p value to a copy of *this and returns the result * * Throws a safe assert if the colorspaces of the two colors are different */ KoColor added(const KoColor &value) const; /** * Serialize this color following Create's swatch color specification available - * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format + * at https://web.archive.org/web/20110826002520/http://create.freedesktop.org/wiki/Swatches_-_colour_file_format/Draft * * This function doesn't create the \ element but rather the \, * \, \ ... elements. It is assumed that colorElt is the \ * element. * * @param colorElt root element for the serialization, it is assumed that this * element is \ * @param doc is the document containing colorElt */ void toXML(QDomDocument& doc, QDomElement& colorElt) const; /** * Unserialize a color following Create's swatch color specification available - * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format + * at https://web.archive.org/web/20110826002520/http://create.freedesktop.org/wiki/Swatches_-_colour_file_format/Draft * * @param elt the element to unserialize (\, \, \) * @param channelDepthId the bit depth is unspecified by the spec, this allow to select * a preferred bit depth for creating the KoColor object (if that * bit depth isn't available, this function will randomly select * an other bit depth) * @return the unserialize color, or an empty color object if the function failed * to unserialize the color */ static KoColor fromXML(const QDomElement& elt, const QString & channelDepthId); /** * Unserialize a color following Create's swatch color specification available - * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format + * at https://web.archive.org/web/20110826002520/http://create.freedesktop.org/wiki/Swatches_-_colour_file_format/Draft * * @param elt the element to unserialize (\, \, \) * @param channelDepthId the bit depth is unspecified by the spec, this allow to select * a preferred bit depth for creating the KoColor object (if that * bit depth isn't available, this function will randomly select * an other bit depth) * @param ok If a an error occurs, *ok is set to false; otherwise it's set to true * @return the unserialize color, or an empty color object if the function failed * to unserialize the color */ static KoColor fromXML(const QDomElement& elt, const QString & channelDepthId, bool* ok); /** * @brief toXML creates a string with XML that represents the current color. The XML * is extended with a "channeldepth" attribute so we can restore the color to the same * channel depth. * @return a valid XML document in a string */ QString toXML() const; /** * @brief fromXML restores a KoColor from a string saved with toXML(). If the * string does not contain the "channeldepth" attribute, 16 bit integer is assumed. * @param xml a valid XML document * @return a new KoColor object */ static KoColor fromXML(const QString &xml); /** * @brief toQString create a user-visible string of the channel names and the channel values * @param color the color to create the string from * @return a string that can be used to display the values of this color to the user. */ static QString toQString(const KoColor &color); #ifndef NODEBUG /// use qDebug calls to print internal info void dump() const; #endif private: inline void assertPermanentColorspace() { #ifndef NODEBUG if (m_colorSpace) { Q_ASSERT(*m_colorSpace == *KoColorSpaceRegistry::instance()->permanentColorspace(m_colorSpace)); } #endif } const KoColorSpace *m_colorSpace; quint8 m_data[MAX_PIXEL_SIZE]; quint8 m_size; }; Q_DECLARE_METATYPE(KoColor) KRITAPIGMENT_EXPORT QDebug operator<<(QDebug dbg, const KoColor &color); #endif diff --git a/libs/pigment/KoColorSpace.h b/libs/pigment/KoColorSpace.h index 036760e00d..df314198c4 100644 --- a/libs/pigment/KoColorSpace.h +++ b/libs/pigment/KoColorSpace.h @@ -1,646 +1,646 @@ /* * Copyright (c) 2005 Boudewijn Rempt * Copyright (c) 2006-2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCOLORSPACE_H #define KOCOLORSPACE_H #include #include #include #include #include #include #include "KoColorSpaceConstants.h" #include "KoColorConversionTransformation.h" #include "KoColorProofingConversionTransformation.h" #include "KoCompositeOp.h" #include #include "kritapigment_export.h" class QDomDocument; class QDomElement; class KoChannelInfo; class KoColorProfile; class KoColorTransformation; class QBitArray; enum Deletability { OwnedByRegistryDoNotDelete, OwnedByRegistryRegistryDeletes, NotOwnedByRegistry }; enum ColorSpaceIndependence { FULLY_INDEPENDENT, TO_LAB16, TO_RGBA8, TO_RGBA16 }; class KoMixColorsOp; class KoConvolutionOp; /** * A KoColorSpace is the definition of a certain color space. * * A color model and a color space are two related concepts. A color * model is more general in that it describes the channels involved and * how they in broad terms combine to describe a color. Examples are * RGB, HSV, CMYK. * * A color space is more specific in that it also describes exactly how * the channels are combined. So for each color model there can be a * number of specific color spaces. So RGB is the model and sRGB, * adobeRGB, etc are colorspaces. * * In Pigment KoColorSpace acts as both a color model and a color space. * You can think of the class definition as the color model, but the * instance of the class as representing a colorspace. * * A third concept is the profile represented by KoColorProfile. It * represents the info needed to specialize a color model into a color * space. * * KoColorSpace is an abstract class serving as an interface. * * Subclasses implement actual color spaces * Some subclasses implement only some parts and are named Traits * */ class KRITAPIGMENT_EXPORT KoColorSpace : public boost::equality_comparable { friend class KoColorSpaceRegistry; friend class KoColorSpaceFactory; protected: /// Only for use by classes that serve as baseclass for real color spaces KoColorSpace(); public: /// Should be called by real color spaces KoColorSpace(const QString &id, const QString &name, KoMixColorsOp* mixColorsOp, KoConvolutionOp* convolutionOp); virtual bool operator==(const KoColorSpace& rhs) const; protected: virtual ~KoColorSpace(); public: //========== Gamut and other basic info ===================================// /* * @returns QPolygonF with 5*channel samples converted to xyY. * maybe convert to 3d space in future? */ QPolygonF gamutXYY() const; /* * @returns a polygon with 5 samples per channel converted to xyY, but unlike * gamutxyY it focuses on the luminance. This then can be used to visualise * the approximate trc of a given colorspace. */ QPolygonF estimatedTRCXYY() const; QVector lumaCoefficients() const; //========== Channels =====================================================// /// Return a list describing all the channels this color model has. The order /// of the channels in the list is the order of channels in the pixel. To find /// out the preferred display position, use KoChannelInfo::displayPosition. QList channels() const; /** * The total number of channels for a single pixel in this color model */ virtual quint32 channelCount() const = 0; /** * Position of the alpha channel in a pixel */ virtual quint32 alphaPos() const = 0; /** * The total number of color channels (excludes alpha) for a single * pixel in this color model. */ virtual quint32 colorChannelCount() const = 0; /** * returns a QBitArray that contains true for the specified * channel types: * * @param color if true, set all color channels to true * @param alpha if true, set all alpha channels to true * * The order of channels is the colorspace descriptive order, * not the pixel order. */ QBitArray channelFlags(bool color = true, bool alpha = false) const; /** * The size in bytes of a single pixel in this color model */ virtual quint32 pixelSize() const = 0; /** * Return a string with the channel's value suitable for display in the gui. */ virtual QString channelValueText(const quint8 *pixel, quint32 channelIndex) const = 0; /** * Return a string with the channel's value with integer * channels normalised to the floating point range 0 to 1, if * appropriate. */ virtual QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) const = 0; /** * Return a QVector of floats with channels' values normalized * to floating point range 0 to 1. */ virtual void normalisedChannelsValue(const quint8 *pixel, QVector &channels) const = 0; /** * Write in the pixel the value from the normalized vector. */ virtual void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) const = 0; /** * Convert the value of the channel at the specified position into * an 8-bit value. The position is not the number of bytes, but * the position of the channel as defined in the channel info list. */ virtual quint8 scaleToU8(const quint8 * srcPixel, qint32 channelPos) const = 0; /** * Set dstPixel to the pixel containing only the given channel of srcPixel. The remaining channels * should be set to whatever makes sense for 'empty' channels of this color space, * with the intent being that the pixel should look like it only has the given channel. */ virtual void singleChannelPixel(quint8 *dstPixel, const quint8 *srcPixel, quint32 channelIndex) const = 0; //========== Identification ===============================================// /** * ID for use in files and internally: unchanging name. As the id must be unique * it is usually the concatenation of the id of the color model and of the color * depth, for instance "RGBA8" or "CMYKA16" or "XYZA32f". */ QString id() const; /** * User visible name which contains the name of the color model and of the color depth. * For instance "RGBA (8-bits)" or "CMYKA (16-bits)". */ QString name() const; /** * @return a string that identify the color model (for instance "RGB" or "CMYK" ...) * @see KoColorModelStandardIds.h */ virtual KoID colorModelId() const = 0; /** * @return a string that identify the bit depth (for instance "U8" or "F16" ...) * @see KoColorModelStandardIds.h */ virtual KoID colorDepthId() const = 0; /** * @return true if the profile given in argument can be used by this color space */ virtual bool profileIsCompatible(const KoColorProfile* profile) const = 0; /** * If false, images in this colorspace will degrade considerably by * functions, tools and filters that have the given measure of colorspace * independence. * * @param independence the measure to which this colorspace will suffer * from the manipulations of the tool or filter asking * @return false if no degradation will take place, true if degradation will * take place */ virtual bool willDegrade(ColorSpaceIndependence independence) const = 0; //========== Capabilities =================================================// /** * Tests if the colorspace offers the specific composite op. */ virtual bool hasCompositeOp(const QString & id) const; /** * Returns the list of user-visible composite ops supported by this colorspace. */ virtual QList compositeOps() const; /** * Retrieve a single composite op from the ones this colorspace offers. * If the requeste composite op does not exist, COMPOSITE_OVER is returned. */ const KoCompositeOp * compositeOp(const QString & id) const; /** * add a composite op to this colorspace. */ virtual void addCompositeOp(const KoCompositeOp * op); /** * Returns true if the colorspace supports channel values outside the * (normalised) range 0 to 1. */ virtual bool hasHighDynamicRange() const = 0; //========== Display profiles =============================================// /** * Return the profile of this color space. */ virtual const KoColorProfile * profile() const = 0; //================= Conversion functions ==================================// /** * The fromQColor methods take a given color defined as an RGB QColor * and fills a byte array with the corresponding color in the * the colorspace managed by this strategy. * * @param color the QColor that will be used to fill dst * @param dst a pointer to a pixel * @param profile the optional profile that describes the color values of QColor */ virtual void fromQColor(const QColor& color, quint8 *dst, const KoColorProfile * profile = 0) const = 0; /** * The toQColor methods take a byte array that is at least pixelSize() long * and converts the contents to a QColor, using the given profile as a source * profile and the optional profile as a destination profile. * * @param src a pointer to the source pixel * @param c the QColor that will be filled with the color at src * @param profile the optional profile that describes the color in c, for instance the monitor profile */ virtual void toQColor(const quint8 *src, QColor *c, const KoColorProfile * profile = 0) const = 0; /** * Convert the pixels in data to (8-bit BGRA) QImage using the specified profiles. * * @param data A pointer to a contiguous memory region containing width * height pixels * @param width in pixels * @param height in pixels * @param dstProfile destination profile * @param renderingIntent the rendering intent * @param conversionFlags conversion flags */ virtual QImage convertToQImage(const quint8 *data, qint32 width, qint32 height, const KoColorProfile * dstProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * Convert the specified data to Lab (D50). All colorspaces are guaranteed to support this * * @param src the source data * @param dst the destination data * @param nPixels the number of source pixels */ virtual void toLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Convert the specified data from Lab (D50). to this colorspace. All colorspaces are * guaranteed to support this. * * @param src the pixels in 16 bit lab format * @param dst the destination data * @param nPixels the number of pixels in the array */ virtual void fromLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Convert the specified data to sRGB 16 bits. All colorspaces are guaranteed to support this * * @param src the source data * @param dst the destination data * @param nPixels the number of source pixels */ virtual void toRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Convert the specified data from sRGB 16 bits. to this colorspace. All colorspaces are * guaranteed to support this. * * @param src the pixels in 16 bit rgb format * @param dst the destination data * @param nPixels the number of pixels in the array */ virtual void fromRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Create a color conversion transformation. */ virtual KoColorConversionTransformation* createColorConverter(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * Convert a byte array of srcLen pixels *src to the specified color space * and put the converted bytes into the prepared byte array *dst. * * Returns false if the conversion failed, true if it succeeded * * This function is not thread-safe. If you want to apply multiple conversion * in different threads at the same time, you need to create one color converter * per-thread using createColorConverter. */ virtual bool convertPixelsTo(const quint8 * src, quint8 * dst, const KoColorSpace * dstColorSpace, quint32 numPixels, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; virtual KoColorConversionTransformation *createProofingTransform(const KoColorSpace * dstColorSpace, const KoColorSpace * proofingSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::Intent proofingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, quint8 *gamutWarning, double adaptationState) const; /** * @brief proofPixelsTo * @param src source * @param dst destination * @param numPixels the amount of pixels. * @param proofingTransform the intent used for proofing. * @return */ virtual bool proofPixelsTo(const quint8 * src, quint8 * dst, quint32 numPixels, KoColorConversionTransformation *proofingTransform) const; //============================== Manipulation functions ==========================// // // The manipulation functions have default implementations that _convert_ the pixel // to a QColor and back. Reimplement these methods in your color strategy! // /** * Get the alpha value of the given pixel, downscaled to an 8-bit value. */ virtual quint8 opacityU8(const quint8 * pixel) const = 0; virtual qreal opacityF(const quint8 * pixel) const = 0; /** * Set the alpha channel of the given run of pixels to the given value. * * pixels -- a pointer to the pixels that will have their alpha set to this value * alpha -- a downscaled 8-bit value for opacity * nPixels -- the number of pixels * */ virtual void setOpacity(quint8 * pixels, quint8 alpha, qint32 nPixels) const = 0; virtual void setOpacity(quint8 * pixels, qreal alpha, qint32 nPixels) const = 0; /** * Multiply the alpha channel of the given run of pixels by the given value. * * pixels -- a pointer to the pixels that will have their alpha set to this value * alpha -- a downscaled 8-bit value for opacity * nPixels -- the number of pixels * */ virtual void multiplyAlpha(quint8 * pixels, quint8 alpha, qint32 nPixels) const = 0; /** * Applies the specified 8-bit alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; the alpha values * are assumed to be 8-bits. */ virtual void applyAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) const = 0; /** * Applies the inverted 8-bit alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; the alpha values * are assumed to be 8-bits. */ virtual void applyInverseAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) const = 0; /** * Applies the specified float alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; alpha values have to be between 0.0 and 1.0 */ virtual void applyAlphaNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) const = 0; /** * Applies the inverted specified float alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; alpha values have to be between 0.0 and 1.0 */ virtual void applyInverseNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) const = 0; /** * Create an adjustment object for adjusting the brightness and contrast * transferValues is a 256 bins array with values from 0 to 0xFFFF * This function is thread-safe, but you need to create one KoColorTransformation per thread. */ virtual KoColorTransformation *createBrightnessContrastAdjustment(const quint16 *transferValues) const = 0; /** * Create an adjustment object for adjusting individual channels * transferValues is an array of colorChannelCount number of 256 bins array with values from 0 to 0xFFFF * This function is thread-safe, but you need to create one KoColorTransformation per thread. * * The layout of the channels must be the following: * * 0..N-2 - color channels of the pixel; * N-1 - alpha channel of the pixel (if exists) */ virtual KoColorTransformation *createPerChannelAdjustment(const quint16 * const* transferValues) const = 0; /** * Darken all color channels with the given amount. If compensate is true, * the compensation factor will be used to limit the darkening. * */ virtual KoColorTransformation *createDarkenAdjustment(qint32 shade, bool compensate, qreal compensation) const = 0; /** * Invert color channels of the given pixels * This function is thread-safe, but you need to create one KoColorTransformation per thread. */ virtual KoColorTransformation *createInvertTransformation() const = 0; /** * Get the difference between 2 colors, normalized in the range (0,255). Only completely * opaque and completely transparent are taken into account when computing the difference; * other transparency levels are not regarded when finding the difference. */ virtual quint8 difference(const quint8* src1, const quint8* src2) const = 0; /** * Get the difference between 2 colors, normalized in the range (0,255). This function * takes the Alpha channel of the pixel into account. Alpha channel has the same * weight as Lightness channel. */ virtual quint8 differenceA(const quint8* src1, const quint8* src2) const = 0; /** * @return the mix color operation of this colorspace (do not delete it locally, it's deleted by the colorspace). */ virtual KoMixColorsOp* mixColorsOp() const; /** * @return the convolution operation of this colorspace (do not delete it locally, it's deleted by the colorspace). */ virtual KoConvolutionOp* convolutionOp() const; /** * Calculate the intensity of the given pixel, scaled down to the range 0-255. XXX: Maybe this should be more flexible */ virtual quint8 intensity8(const quint8 * src) const = 0; /* *increase luminosity by step */ virtual void increaseLuminosity(quint8 * pixel, qreal step) const; virtual void decreaseLuminosity(quint8 * pixel, qreal step) const; virtual void increaseSaturation(quint8 * pixel, qreal step) const; virtual void decreaseSaturation(quint8 * pixel, qreal step) const; virtual void increaseHue(quint8 * pixel, qreal step) const; virtual void decreaseHue(quint8 * pixel, qreal step) const; virtual void increaseRed(quint8 * pixel, qreal step) const; virtual void increaseGreen(quint8 * pixel, qreal step) const; virtual void increaseBlue(quint8 * pixel, qreal step) const; virtual void increaseYellow(quint8 * pixel, qreal step) const; virtual void toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const = 0; virtual QVector fromHSY(qreal *hue, qreal *sat, qreal *luma) const = 0; virtual void toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const = 0; virtual QVector fromYUV(qreal *y, qreal *u, qreal *v) const = 0; /** * Compose two arrays of pixels together. If source and target * are not the same color model, the source pixels will be * converted to the target model. We're "dst" -- "dst" pixels are always in _this_ * colorspace. * * @param srcSpace the colorspace of the source pixels that will be composited onto "us" * @param params the information needed for blitting e.g. the source and destination pixel data, * the opacity and flow, ... * @param op the composition operator to use, e.g. COPY_OVER * @param renderingIntent the rendering intent * @param conversionFlags the conversion flags. * */ virtual void bitBlt(const KoColorSpace* srcSpace, const KoCompositeOp::ParameterInfo& params, const KoCompositeOp* op, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * Serialize this color following Create's swatch color specification available - * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format + * at https://web.archive.org/web/20110826002520/http://create.freedesktop.org/wiki/Swatches_-_colour_file_format/Draft * * This function doesn't create the \ element but rather the \, * \, \ ... elements. It is assumed that colorElt is the \ * element. * * @param pixel buffer to serialized * @param colorElt root element for the serialization, it is assumed that this * element is \ * @param doc is the document containing colorElt */ virtual void colorToXML(const quint8* pixel, QDomDocument& doc, QDomElement& colorElt) const = 0; /** * Unserialize a color following Create's swatch color specification available - * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format + * at https://web.archive.org/web/20110826002520/http://create.freedesktop.org/wiki/Swatches_-_colour_file_format/Draft * * @param pixel buffer where the color will be unserialized * @param elt the element to unserialize (\, \, \) * @return the unserialize color, or an empty color object if the function failed * to unserialize the color */ virtual void colorFromXML(quint8* pixel, const QDomElement& elt) const = 0; KoColorTransformation* createColorTransformation(const QString & id, const QHash & parameters) const; protected: /** * Use this function in the constructor of your colorspace to add the information about a channel. * @param ci a pointer to the information about a channel */ virtual void addChannel(KoChannelInfo * ci); const KoColorConversionTransformation* toLabA16Converter() const; const KoColorConversionTransformation* fromLabA16Converter() const; const KoColorConversionTransformation* toRgbA16Converter() const; const KoColorConversionTransformation* fromRgbA16Converter() const; /** * Returns the thread-local conversion cache. If it doesn't exist * yet, it is created. If it is currently too small, it is resized. */ QVector * threadLocalConversionCache(quint32 size) const; /** * This function defines the behavior of the bitBlt function * when the composition of pixels in different colorspaces is * requested, that is in case: * * srcCS == any * dstCS == this * * 1) preferCompositionInSourceColorSpace() == false, * * the source pixels are first converted to *this color space * and then composition is performed. * * 2) preferCompositionInSourceColorSpace() == true, * * the destination pixels are first converted into *srcCS color * space, then the composition is done, and the result is finally * converted into *this colorspace. * * This is used by alpha8() color space mostly, because it has * weaker representation of the color, so the composition * should be done in CS with richer functionality. */ virtual bool preferCompositionInSourceColorSpace() const; struct Private; Private * const d; }; inline QDebug operator<<(QDebug dbg, const KoColorSpace *cs) { if (cs) { dbg.nospace() << cs->name() << " (" << cs->colorModelId().id() << "," << cs->colorDepthId().id() << " )"; } else { dbg.nospace() << "0x0"; } return dbg.space(); } #endif // KOCOLORSPACE_H diff --git a/libs/pigment/KoLabColorSpaceTraits.h b/libs/pigment/KoLabColorSpaceTraits.h index 925aba8f1c..eba27527d5 100644 --- a/libs/pigment/KoLabColorSpaceTraits.h +++ b/libs/pigment/KoLabColorSpaceTraits.h @@ -1,400 +1,400 @@ /* * Copyright (c) 2006-2007 Cyrille Berger - * Copyright (c) 2016 L. E. Segovia + * Copyright (c) 2016 L. E. Segovia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KO_LAB_COLORSPACE_TRAITS_H_ #define _KO_LAB_COLORSPACE_TRAITS_H_ /** * LAB traits, it provides some convenient functions to * access LAB channels through an explicit API. * * Use this class in conjonction with KoColorSpace::toLabA16 and * KoColorSpace::fromLabA16 data. * * Example: * quint8* p = KoLabU16Traits::allocate(1); * oneKoColorSpace->toLabA16(somepointertodata, p, 1); * KoLabU16Traits::setL( p, KoLabU16Traits::L(p) / 10 ); * oneKoColorSpace->fromLabA16(p, somepointertodata, 1); */ template struct KoLabTraits : public KoColorSpaceTrait<_channels_type_, 4, 3> { typedef _channels_type_ channels_type; typedef KoColorSpaceTrait<_channels_type_, 4, 3> parent; static const qint32 L_pos = 0; static const qint32 a_pos = 1; static const qint32 b_pos = 2; /** * An Lab pixel */ struct Pixel { channels_type L; channels_type a; channels_type b; channels_type alpha; }; /// @return the L component inline static channels_type L(quint8* data) { channels_type* d = parent::nativeArray(data); return d[L_pos]; } /// Set the L component inline static void setL(quint8* data, channels_type nv) { channels_type* d = parent::nativeArray(data); d[L_pos] = nv; } /// @return the a component inline static channels_type a(quint8* data) { channels_type* d = parent::nativeArray(data); return d[a_pos]; } /// Set the a component inline static void setA(quint8* data, channels_type nv) { channels_type* d = parent::nativeArray(data); d[a_pos] = nv; } /// @return the b component inline static channels_type b(quint8* data) { channels_type* d = parent::nativeArray(data); return d[b_pos]; } /// Set the a component inline static void setB(quint8* data, channels_type nv) { channels_type* d = parent::nativeArray(data); d[b_pos] = nv; } }; //For quint* values must range from 0 to 1 - see KoColorSpaceMaths struct KoLabU8Traits : public KoLabTraits { static const quint32 MAX_CHANNEL_L = 100; static const quint32 MAX_CHANNEL_AB = 255; static const quint32 CHANNEL_AB_ZERO_OFFSET = 128; inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { c = nativeArray(pixel)[i]; switch (i) { case L_pos: channels[i] = ((qreal)c) / MAX_CHANNEL_L; break; case a_pos: channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB; break; case b_pos: channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB; break; case 3: channels[i] = ((qreal)c) / UINT16_MAX; break; default: channels[i] = ((qreal)c) / KoColorSpaceMathsTraits::unitValue; break; } } } inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { if (channelIndex > parent::channels_nb) return QString("Error"); channels_type c = nativeArray(pixel)[channelIndex]; switch (channelIndex) { case L_pos: return QString().setNum(100.0 * ((qreal)c) / MAX_CHANNEL_L); case a_pos: return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB)); case b_pos: return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB)); case 3: return QString().setNum(100.0 * ((qreal)c) / UINT16_MAX); default: return QString("Error"); } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < channels_nb; i++) { float b = 0; switch (i) { case L_pos: b = qBound((float)0, (float)MAX_CHANNEL_L * values[i], (float)MAX_CHANNEL_L); break; case a_pos: case b_pos: b = qBound((float)0, (float)MAX_CHANNEL_AB * values[i] + CHANNEL_AB_ZERO_OFFSET, (float)MAX_CHANNEL_AB); break; default: b = qBound((float)KoColorSpaceMathsTraits::min, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)KoColorSpaceMathsTraits::max); break; } c = (channels_type)b; nativeArray(pixel)[i] = c; } } }; struct KoLabU16Traits : public KoLabTraits { // https://github.com/mm2/Little-CMS/blob/master/src/cmspcs.c static const quint32 MAX_CHANNEL_L = 0xFF00; static const quint32 MAX_CHANNEL_AB = 0xFFFF; static const quint32 CHANNEL_AB_ZERO_OFFSET = 0x8000; inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { c = nativeArray(pixel)[i]; switch (i) { case L_pos: channels[i] = ((qreal)c) / MAX_CHANNEL_L; break; case a_pos: channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB; break; case b_pos: channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB; break; case 3: channels[i] = ((qreal)c) / UINT16_MAX; break; default: channels[i] = ((qreal)c) / KoColorSpaceMathsTraits::unitValue; break; } } } inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { if (channelIndex > parent::channels_nb) return QString("Error"); channels_type c = nativeArray(pixel)[channelIndex]; switch (channelIndex) { case L_pos: return QString().setNum(100.0 * ((qreal)c) / MAX_CHANNEL_L); case a_pos: return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB)); case b_pos: return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB)); case 3: return QString().setNum(100.0 * ((qreal)c) / UINT16_MAX); default: return QString("Error"); } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < channels_nb; i++) { float b = 0; switch (i) { case L_pos: b = qBound((float)0, (float)MAX_CHANNEL_L * values[i], (float)MAX_CHANNEL_L); break; case a_pos: case b_pos: b = qBound((float)0, (float)MAX_CHANNEL_AB * values[i] + CHANNEL_AB_ZERO_OFFSET, (float)MAX_CHANNEL_AB); break; default: b = qBound((float)KoColorSpaceMathsTraits::min, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)KoColorSpaceMathsTraits::max); break; } c = (channels_type)b; nativeArray(pixel)[i] = c; } } }; // Float values are not normalised // XXX: is it really necessary to bind them to these ranges? #include #ifdef HAVE_OPENEXR #include struct KoLabF16Traits : public KoLabTraits { static constexpr float MIN_CHANNEL_L = 0; static constexpr float MAX_CHANNEL_L = 100; static constexpr float MIN_CHANNEL_AB = -128; static constexpr float MAX_CHANNEL_AB = +127; // Lab has some... particulars // For instance, float et al. are NOT normalised inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { return channelValueText(pixel, channelIndex); } inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { c = parent::nativeArray(pixel)[i]; channels[i] = (qreal)c; } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { float b = 0; switch(i) { case L_pos: b = qBound((float)MIN_CHANNEL_L, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)MAX_CHANNEL_L); break; case a_pos: case b_pos: b = qBound((float)MIN_CHANNEL_AB, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)MAX_CHANNEL_AB); break; case 3: b = qBound((float)KoColorSpaceMathsTraits::min, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)KoColorSpaceMathsTraits::max); default: break; } c = (channels_type)b; parent::nativeArray(pixel)[i] = c; } } }; #endif struct KoLabF32Traits : public KoLabTraits { static constexpr float MIN_CHANNEL_L = 0; static constexpr float MAX_CHANNEL_L = 100; static constexpr float MIN_CHANNEL_AB = -128; static constexpr float MAX_CHANNEL_AB = +127; // Lab has some... particulars inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { return channelValueText(pixel, channelIndex); } inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { c = parent::nativeArray(pixel)[i]; channels[i] = (qreal)c; } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { float b = 0; switch(i) { case L_pos: b = qBound((float)MIN_CHANNEL_L, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)MAX_CHANNEL_L); break; case a_pos: case b_pos: b = qBound((float)MIN_CHANNEL_AB, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)MAX_CHANNEL_AB); break; case 3: b = qBound((float)KoColorSpaceMathsTraits::min, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)KoColorSpaceMathsTraits::max); default: break; } c = (channels_type)b; parent::nativeArray(pixel)[i] = c; } } }; struct KoLabF64Traits : public KoLabTraits { static constexpr double MIN_CHANNEL_L = 0; static constexpr double MAX_CHANNEL_L = 100; static constexpr double MIN_CHANNEL_AB = -128; static constexpr double MAX_CHANNEL_AB = +127; // Lab has some... particulars inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { return channelValueText(pixel, channelIndex); } inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { c = parent::nativeArray(pixel)[i]; channels[i] = (qreal)c; } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() >= (int)parent::channels_nb); channels_type c; for (uint i = 0; i < parent::channels_nb; i++) { float b = 0; switch(i) { case L_pos: b = qBound((float)MIN_CHANNEL_L, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)MAX_CHANNEL_L); break; case a_pos: case b_pos: b = qBound((float)MIN_CHANNEL_AB, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)MAX_CHANNEL_AB); break; case 3: b = qBound((float)KoColorSpaceMathsTraits::min, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)KoColorSpaceMathsTraits::max); default: break; } c = (channels_type)b; parent::nativeArray(pixel)[i] = c; } } }; #endif diff --git a/libs/pigment/KoLut.h b/libs/pigment/KoLut.h index a92a88d5fd..e9213ad1c7 100644 --- a/libs/pigment/KoLut.h +++ b/libs/pigment/KoLut.h @@ -1,33 +1,33 @@ /* * Copyright (c) 2010 Cyrille Berger #define _USE_QT_TYPES_ namespace Ko { #include "lut.h" } #endif diff --git a/libs/pigment/compositeops/KoCompositeOpFunctions.h b/libs/pigment/compositeops/KoCompositeOpFunctions.h index 7357238e0e..80c242c356 100644 --- a/libs/pigment/compositeops/KoCompositeOpFunctions.h +++ b/libs/pigment/compositeops/KoCompositeOpFunctions.h @@ -1,952 +1,952 @@ /* * Copyright (c) 2011 Silvio Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCOMPOSITEOP_FUNCTIONS_H_ #define KOCOMPOSITEOP_FUNCTIONS_H_ #include template inline void cfReorientedNormalMapCombine(TReal srcR, TReal srcG, TReal srcB, TReal& dstR, TReal& dstG, TReal& dstB) { - // see http://blog.selfshadow.com/publications/blending-in-detail/ by Barre-Brisebois and Hill + // see https://blog.selfshadow.com/publications/blending-in-detail/ by Barre-Brisebois and Hill TReal tx = 2*srcR-1; TReal ty = 2*srcG-1; TReal tz = 2*srcB; TReal ux = -2*dstR+1; TReal uy = -2*dstG+1; TReal uz = 2*dstB-1; TReal k = (tx*ux+ty*uy+tz*uz)/tz; // dot(t,u)/t.z TReal rx = tx*k-ux; TReal ry = ty*k-uy; TReal rz = tz*k-uz; k = 1/sqrt(rx*rx+ry*ry+rz*rz); // normalize result rx *= k; ry *= k; rz *= k; dstR = rx*0.5+0.5; dstG = ry*0.5+0.5; dstB = rz*0.5+0.5; } template inline void cfColor(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { TReal lum = getLightness(dr, dg, db); dr = sr; dg = sg; db = sb; setLightness(dr, dg, db, lum); } template inline void cfLightness(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { setLightness(dr, dg, db, getLightness(sr, sg, sb)); } template inline void cfIncreaseLightness(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { addLightness(dr, dg, db, getLightness(sr, sg, sb)); } template inline void cfDecreaseLightness(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { addLightness(dr, dg, db, getLightness(sr, sg, sb) - TReal(1.0)); } template inline void cfSaturation(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { TReal sat = getSaturation(sr, sg, sb); TReal light = getLightness(dr, dg, db); setSaturation(dr, dg, db, sat); setLightness(dr, dg, db, light); } template inline void cfIncreaseSaturation(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { using namespace Arithmetic; TReal sat = lerp(getSaturation(dr,dg,db), unitValue(), getSaturation(sr,sg,sb)); TReal light = getLightness(dr, dg, db); setSaturation(dr, dg, db, sat); setLightness(dr, dg, db, light); } template inline void cfDecreaseSaturation(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { using namespace Arithmetic; TReal sat = lerp(zeroValue(), getSaturation(dr,dg,db), getSaturation(sr,sg,sb)); TReal light = getLightness(dr, dg, db); setSaturation(dr, dg, db, sat); setLightness(dr, dg, db, light); } template inline void cfHue(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { TReal sat = getSaturation(dr, dg, db); TReal lum = getLightness(dr, dg, db); dr = sr; dg = sg; db = sb; setSaturation(dr, dg, db, sat); setLightness(dr, dg, db, lum); } template inline void cfTangentNormalmap(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { using namespace Arithmetic; TReal half=halfValue(); dr = sr+(dr-half); dg = sg+(dg-half); db = sb+(db-unitValue()); } template inline void cfDarkerColor(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { TReal lum = getLightness(dr, dg, db); TReal lum2 = getLightness(sr, sg, sb); if (lum inline void cfLighterColor(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) { TReal lum = getLightness(dr, dg, db); TReal lum2 = getLightness(sr, sg, sb); if (lum>lum2) { sr = dr; sg = dg; sb = db; } else { dr = sr; dg = sg; db = sb; } } template inline T cfColorBurn(T src, T dst) { using namespace Arithmetic; if(dst == unitValue()) return unitValue(); T invDst = inv(dst); if(src < invDst) return zeroValue(); return inv(clamp(div(invDst, src))); } template inline T cfLinearBurn(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return clamp(composite_type(src) + dst - unitValue()); } template inline T cfColorDodge(T src, T dst) { using namespace Arithmetic; //Fixing Color Dodge to avoid ZX Colors on bright area. if(src == unitValue()) return unitValue(); T invSrc = inv(src); if(invSrc == zeroValue()) return unitValue(); return Arithmetic::clamp(div(dst, invSrc)); } template inline T cfAddition(T src, T dst) { typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return Arithmetic::clamp(composite_type(src) + dst); } template inline T cfSubtract(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return clamp(composite_type(dst) - src); } template inline T cfInverseSubtract(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return clamp(composite_type(dst) - inv(src)); } template inline T cfExclusion(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; composite_type x = mul(src, dst); return clamp(composite_type(dst) + src - (x + x)); } template inline T cfDivide(T src, T dst) { using namespace Arithmetic; //typedef typename KoColorSpaceMathsTraits::compositetype composite_type; if(src == zeroValue()) return (dst == zeroValue()) ? zeroValue() : unitValue(); return clamp(div(dst, src)); } template inline T cfHardLight(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; composite_type src2 = composite_type(src) + src; if(src > halfValue()) { // screen(src*2.0 - 1.0, dst) src2 -= unitValue(); // src2 is guaranteed to be smaller than unitValue() now return Arithmetic::unionShapeOpacity(T(src2), dst); } // src2 is guaranteed to be smaller than unitValue() due to 'if' return Arithmetic::mul(T(src2), dst); } template inline T cfSoftLightSvg(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); if(fsrc > 0.5f) { qreal D = (fdst > 0.25f) ? sqrt(fdst) : ((16.0f*fdst - 12.0)*fdst + 4.0f)*fdst; return scale(fdst + (2.0f*fsrc - 1.0f) * (D - fdst)); } return scale(fdst - (1.0f - 2.0f * fsrc) * fdst * (1.0f - fdst)); } template inline T cfSoftLight(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); if(fsrc > 0.5f) { return scale(fdst + (2.0f * fsrc - 1.0f) * (sqrt(fdst) - fdst)); } return scale(fdst - (1.0f - 2.0f*fsrc) * fdst * (1.0f - fdst)); } template inline T cfVividLight(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; if(src < halfValue()) { if(src == zeroValue()) return (dst == unitValue()) ? unitValue() : zeroValue(); // min(1,max(0,1-(1-dst) / (2*src))) composite_type src2 = composite_type(src) + src; composite_type dsti = inv(dst); return clamp(unitValue() - (dsti * unitValue() / src2)); } if(src == unitValue()) return (dst == zeroValue()) ? zeroValue() : unitValue(); // min(1,max(0, dst / (2*(1-src))) composite_type srci2 = inv(src); srci2 += srci2; return clamp(composite_type(dst) * unitValue() / srci2); } template inline T cfPinLight(T src, T dst) { typedef typename KoColorSpaceMathsTraits::compositetype composite_type; // TODO: verify that the formula is correct (the first max would be useless here) // max(0, max(2*src-1, min(dst, 2*src))) composite_type src2 = composite_type(src) + src; composite_type a = qMin(dst, src2); composite_type b = qMax(src2-Arithmetic::unitValue(), a); return T(b); } template inline T cfArcTangent(T src, T dst) { using namespace Arithmetic; if(dst == zeroValue()) return (src == zeroValue()) ? zeroValue() : unitValue(); return scale(2.0 * atan(scale(src) / scale(dst)) / Arithmetic::pi); } template inline T cfAllanon(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; // (dst + src) / 2 [or (dst + src) * 0.5] return T((composite_type(src) + dst) * halfValue() / unitValue()); } template inline T cfLinearLight(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; // min(1,max(0,(dst + 2*src)-1)) return clamp((composite_type(src) + src + dst) - unitValue()); } template inline T cfParallel(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; // min(max(2 / (1/dst + 1/src), 0), 1) composite_type unit = unitValue(); composite_type s = (src != zeroValue()) ? div(unit, src) : unit; composite_type d = (dst != zeroValue()) ? div(unit, dst) : unit; if (src == zeroValue()) { return zeroValue(); } if (dst == zeroValue()) { return zeroValue(); } return clamp((unit+unit) * unit / (d+s)); } template inline T cfEquivalence(T src, T dst) { typedef typename KoColorSpaceMathsTraits::compositetype composite_type; // 1 - abs(dst - src) composite_type x = composite_type(dst) - src; return (x < Arithmetic::zeroValue()) ? T(-x) : T(x); } template inline T cfGrainMerge(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return clamp(composite_type(dst) + src - halfValue()); } template inline T cfGrainExtract(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; return clamp(composite_type(dst) - src + halfValue()); } template inline T cfHardMix(T src, T dst) { return (dst > Arithmetic::halfValue()) ? cfColorDodge(src,dst) : cfColorBurn(src,dst); } template inline T cfHardMixPhotoshop(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; const composite_type sum = composite_type(src) + dst; return sum > unitValue() ? unitValue() : zeroValue(); } template inline T cfAdditiveSubtractive(T src, T dst) { using namespace Arithmetic; // min(1,max(0,abs(sqr(CB)-sqr(CT)))) qreal x = sqrt(scale(dst)) - sqrt(scale(src)); return scale((x < 0.0) ? -x : x); } template inline T cfGammaDark(T src, T dst) { using namespace Arithmetic; if(src == zeroValue()) return zeroValue(); // power(dst, 1/src) return scale(pow(scale(dst), 1.0/scale(src))); } template inline T cfGammaLight(T src, T dst) { using namespace Arithmetic; return scale(pow(scale(dst), scale(src))); } template inline T cfGammaIllumination(T src, T dst) { using namespace Arithmetic; return inv(cfGammaDark(inv(src),inv(dst))); } template inline T cfGeometricMean(T src, T dst) { using namespace Arithmetic; return scale(sqrt(scale(dst) * scale(src))); } template inline T cfOver(T src, T dst) { Q_UNUSED(dst); return src; } template inline T cfOverlay(T src, T dst) { return cfHardLight(dst, src); } template inline T cfMultiply(T src, T dst) { return Arithmetic::mul(src, dst); } template inline T cfHardOverlay(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc == 1.0) { return scale(1.0);} if(fsrc > 0.5f) { return scale(cfDivide(inv(2.0 * fsrc - 1.0f), fdst)); } return scale(mul(2.0 * fsrc, fdst)); } template inline T cfDifference(T src, T dst) { return qMax(src,dst) - qMin(src,dst); } template inline T cfScreen(T src, T dst) { return Arithmetic::unionShapeOpacity(src, dst); } template inline T cfDarkenOnly(T src, T dst) { return qMin(src, dst); } template inline T cfLightenOnly(T src, T dst) { return qMax(src, dst); } template inline T cfGlow(T src, T dst) { using namespace Arithmetic; // see http://www.pegtop.net/delphi/articles/blendmodes/quadratic.htm for formulas of Quadratic Blending Modes like Glow, Reflect, Freeze, and Heat if (dst == unitValue()) { return unitValue(); } return clamp(div(mul(src, src), inv(dst))); } template inline T cfReflect(T src, T dst) { using namespace Arithmetic; return clamp(cfGlow(dst,src)); } template inline T cfHeat(T src, T dst) { using namespace Arithmetic; if(src == unitValue()) { return unitValue(); } if(dst == zeroValue()) { return zeroValue(); } return inv(clamp(div(mul(inv(src), inv(src)),dst))); } template inline T cfFreeze(T src, T dst) { using namespace Arithmetic; return (cfHeat(dst,src)); } template inline T cfHelow(T src, T dst) { using namespace Arithmetic; // see http://www.pegtop.net/delphi/articles/blendmodes/quadratic.htm for formulas of Quadratic Blending Modes like Glow, Reflect, Freeze, and Heat if (cfHardMixPhotoshop(src,dst) == unitValue()) { return cfHeat(src,dst); } if (src == zeroValue()) { return zeroValue(); } return (cfGlow(src,dst)); } template inline T cfFrect(T src, T dst) { using namespace Arithmetic; if (cfHardMixPhotoshop(src,dst) == unitValue()) { return cfFreeze(src,dst); } if (dst == zeroValue()) { return zeroValue(); } return (cfReflect(src,dst)); } template inline T cfGleat(T src, T dst) { using namespace Arithmetic; // see http://www.pegtop.net/delphi/articles/blendmodes/quadratic.htm for formulas of Quadratic Blending Modes like Glow, Reflect, Freeze, and Heat if(dst == unitValue()) { return unitValue(); } if(cfHardMixPhotoshop(src,dst) == unitValue()) { return cfGlow(src,dst); } return (cfHeat(src,dst)); } template inline T cfReeze(T src, T dst) { using namespace Arithmetic; return (cfGleat(dst,src)); } template inline T cfFhyrd(T src, T dst) { using namespace Arithmetic; return (cfAllanon(cfFrect(src,dst),cfHelow(src,dst))); } template inline T cfInterpolation(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); if(dst == zeroValue() && src == zeroValue()) { return zeroValue(); } return scale(.5f-.25f*cos(pi*(fsrc))-.25f*cos(pi*(fdst))); } template inline T cfInterpolationB(T src, T dst) { using namespace Arithmetic; return cfInterpolation(cfInterpolation(src,dst),cfInterpolation(src,dst)); } template inline T cfPenumbraB(T src, T dst) { using namespace Arithmetic; if (dst == unitValue()) { return unitValue(); } if (dst + src < unitValue()) { return (cfColorDodge(dst,src)/2); } if (src == zeroValue()) { return zeroValue(); } return inv(clamp(div(inv(dst),src)/2)); } template inline T cfPenumbraD(T src, T dst) { using namespace Arithmetic; if (dst == unitValue()) { return unitValue(); } return cfArcTangent(src,inv(dst)); } template inline T cfPenumbraC(T src, T dst) { using namespace Arithmetic; return cfPenumbraD(dst,src); } template inline T cfPenumbraA(T src, T dst) { using namespace Arithmetic; return (cfPenumbraB(dst,src)); } template inline T cfSoftLightIFSIllusions(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); return scale(pow(fdst,pow(2.0,(mul(2.0,.5f-fsrc))))); } template inline T cfSoftLightPegtopDelphi(T src, T dst) { using namespace Arithmetic; return clamp(cfAddition(mul(dst,cfScreen(src,dst)),mul(mul(src,dst),inv(dst)))); } template inline T cfNegation(T src, T dst) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; composite_type unit = unitValue(); composite_type a = unit - src - dst; composite_type s = abs(a); composite_type d = unit - s; return T(d); } template inline T cfNor(T src, T dst) { using namespace Arithmetic; return and(src,dst); } template inline T cfNand(T src, T dst) { using namespace Arithmetic; return or(src,dst); } template inline T cfXor(T src, T dst) { using namespace Arithmetic; return xor(src,dst); } template inline T cfXnor(T src, T dst) { using namespace Arithmetic; return cfXor(src,inv(dst)); } template inline T cfAnd(T src, T dst) { using namespace Arithmetic; return cfNor(inv(src),inv(dst)); } template inline T cfOr(T src, T dst) { using namespace Arithmetic; return cfNand(inv(src),inv(dst)); } template inline T cfConverse(T src, T dst) { using namespace Arithmetic; return cfOr(inv(src),dst); } template inline T cfNotConverse(T src, T dst) { using namespace Arithmetic; return cfAnd(src,inv(dst)); } template inline T cfImplies(T src, T dst) { using namespace Arithmetic; return cfOr(src,inv(dst)); } template inline T cfNotImplies(T src, T dst) { using namespace Arithmetic; return cfAnd(inv(src),dst); } template inline T cfPNormA(T src, T dst) { using namespace Arithmetic; //This is also known as P-Norm mode with factor of 2.3333 See IMBLEND image blending mode samples, and please see imblend.m file found on Additional Blending Mode thread at Phabricator. 1/2.3333 is .42875... return clamp(pow(pow((float)dst, 2.3333333333333333) + pow((float)src, 2.3333333333333333), 0.428571428571434)); } template inline T cfPNormB(T src, T dst) { using namespace Arithmetic; //This is also known as P-Norm mode with factor of 2.3333 See IMBLEND image blending mode samples, and please see imblend.m file found on Additional Blending Mode thread at Phabricator. 1/2.3333 is .42875... return clamp(pow(pow(dst,4)+pow(src,4),0.25)); } template inline T cfSuperLight(T src, T dst) { using namespace Arithmetic; //4.0 can be adjusted to taste. 4.0 is picked for being the best in terms of contrast and details. See imblend.m file. qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc < .5) { return scale(inv(pow(pow(inv(fdst),2.875)+pow(inv(2.0*fsrc),2.875),1.0/2.875))); } return scale(pow(pow(fdst,2.875)+pow(2.0*fsrc-1.0,2.875),1.0/2.875)); } template inline T cfTintIFSIllusions(T src, T dst) { using namespace Arithmetic; //Known as Light Blending mode found in IFS Illusions. Picked this name because it results into a very strong tint, and has better naming convention. qreal fsrc = scale(src); qreal fdst = scale(dst); return scale(fsrc*inv(fdst)+sqrt(fdst)); } template inline T cfShadeIFSIllusions(T src, T dst) { using namespace Arithmetic; //Known as Shadow Blending mode found in IFS Illusions. Picked this name because it is the opposite of Tint (IFS Illusion Blending mode). qreal fsrc = scale(src); qreal fdst = scale(dst); return scale(inv((inv(fdst)*fsrc)+sqrt(inv(fsrc)))); } template inline T cfFogLightenIFSIllusions(T src, T dst) { using namespace Arithmetic; //Known as Bright Blending mode found in IFS Illusions. Picked this name because the shading reminds me of fog when overlaying with a gradient. qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc < .5) { return scale(inv(inv(fsrc)*fsrc)-inv(fdst)*inv(fsrc)); } return scale(fsrc-inv(fdst)*inv(fsrc)+pow(inv(fsrc),2)); } template inline T cfFogDarkenIFSIllusions(T src, T dst) { using namespace Arithmetic; //Known as Dark Blending mode found in IFS Illusions. Picked this name because the shading reminds me of fog when overlaying with a gradient. qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc < .5) { return scale(inv(fsrc)*fsrc+fsrc*fdst); } return scale(fsrc*fdst+fsrc-pow(fsrc,2)); } template inline T cfModulo(T src, T dst) { using namespace Arithmetic; return mod(dst,src); } template inline T cfModuloShift(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc == 1.0 && fdst == 0.0) { return scale(0.0); } return scale(mod((fdst+fsrc),1.0000000000)); } template inline T cfModuloShiftContinuous(T src, T dst) { using namespace Arithmetic; //This blending mode do not behave like difference/equivalent with destination layer inverted if you use group layer on addition while the content of group layer contains several addition-mode layers, it works as expected on float images. So, no need to change this. qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc == 1.0 && fdst == 0.0) { return scale(1.0); } return scale((int(ceil(fdst+fsrc)) % 2 != 0) || (fdst == zeroValue()) ? cfModuloShift(fsrc,fdst) : inv(cfModuloShift(fsrc,fdst))); } template inline T cfDivisiveModulo(T src, T dst) { using namespace Arithmetic; //I have to use 1.00000 as unitValue failed to work for those area. qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc == zeroValue()) { return scale(mod(((1.0000000000/epsilon()) * fdst),1.0000000000)); } return scale(mod(((1.0000000000/fsrc) * fdst),1.0000000000)); } template inline T cfDivisiveModuloContinuous(T src, T dst) { using namespace Arithmetic; qreal fsrc = scale(src); qreal fdst = scale(dst); if (fdst == zeroValue()) { return zeroValue(); } if (fsrc == zeroValue()) { return cfDivisiveModulo(fsrc,fdst); } return scale( int(ceil(fdst/fsrc)) % 2 != 0 ? cfDivisiveModulo(fsrc,fdst) : inv(cfDivisiveModulo(fsrc,fdst))); } template inline T cfModuloContinuous(T src, T dst) { using namespace Arithmetic; return cfMultiply(cfDivisiveModuloContinuous(src,dst),src); } template inline T cfEasyDodge(T src, T dst) { using namespace Arithmetic; // The 13 divided by 15 can be adjusted to taste. See imgblend.m qreal fsrc = scale(src); qreal fdst = scale(dst); if (fsrc == 1.0) { return scale(1.0);} return scale(pow(fdst,mul(inv(fsrc != 1.0 ? fsrc : .999999999999),1.039999999))); } template inline T cfEasyBurn(T src, T dst) { using namespace Arithmetic; // The 13 divided by 15 can be adjusted to taste. See imgblend.m qreal fsrc = scale(src); qreal fdst = scale(dst); return scale(inv(pow(inv(fsrc != 1.0 ? fsrc : .999999999999),mul(fdst,1.039999999)))); } template inline T cfFlatLight(T src, T dst) { using namespace Arithmetic; if (src == zeroValue()) { return zeroValue(); } return clamp(cfHardMixPhotoshop(inv(src),dst)==unitValue() ? cfPenumbraB(src,dst) : cfPenumbraA(src,dst)); } template inline void cfAdditionSAI(TReal src, TReal sa, TReal& dst, TReal& da) { using namespace Arithmetic; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; Q_UNUSED(da); composite_type newsrc; newsrc = mul(src, sa); dst = clamp(newsrc + dst); } #endif // KOCOMPOSITEOP_FUNCTIONS_H_ diff --git a/libs/pigment/lut.h b/libs/pigment/lut.h index 4812876feb..6f0c885c86 100644 --- a/libs/pigment/lut.h +++ b/libs/pigment/lut.h @@ -1,334 +1,334 @@ /* * Copyright (c) 2010 Cyrille Berger * * 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. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * 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. */ #ifndef _LUT_H_ #define _LUT_H_ template class LutKey; template class FullLutKey; /** * Provide an implementation for a look-up table for a function. Do not use directly, instead * use @ref Lut when you are not interested in having the full look-up table or for floating * point, or use @ref FullLut for 8bits and 16bits when you want to have a full Lut. * * @code * struct MyFunction { * inline static int compute(int i) * { * return 1-i; * } * } * Lut myLut; * @endcode */ template class BaseLut { private: /** * Initialization of the table. */ inline void init() { int size = m_key.size(); m_table = new _OutputT_[size]; for(int i = 0; i < size; ++i) { m_table[i] = m_function(m_key.keyToInput(i)); } } public: /** * Create the lut with the specific key. */ inline BaseLut(_LutKeyT_ key, _FunctionT_ function = _FunctionT_()) : m_key(key), m_function(function) { init(); } inline ~BaseLut() { // just leak on exit -- we get into trouble for explicitly // deleting stuff from static objects, like registries //delete[] m_table; } public: /** * @return the function value for parameter @p i */ inline _OutputT_ operator()(_InputT_ i) const { if(m_key.inrange(i)) { return m_table[m_key.inputToKey(i)]; } return m_function(i); } private: _OutputT_* m_table; _LutKeyT_ m_key; _FunctionT_ m_function; }; /** * This Lut is limited to a range of values. */ template class Lut : public BaseLut<_FunctionT_, _OutputT_, _InputT_, LutKey<_InputT_> > { public: /** * Create the lut between @p _min and @p _max . */ inline Lut(_InputT_ _min, _InputT_ _max, _FunctionT_ function = _FunctionT_()) : BaseLut<_FunctionT_, _OutputT_, _InputT_, LutKey<_InputT_> >( LutKey<_InputT_>(_min, _max), function) { } inline Lut(LutKey<_InputT_> key, _FunctionT_ function = _FunctionT_()) : BaseLut<_FunctionT_, _OutputT_, _InputT_, LutKey<_InputT_> >( key, function) { } }; /** * This Lut has precomputed values for all elements. */ template class FullLut : public BaseLut<_FunctionT_, _OutputT_, _InputT_, FullLutKey<_InputT_> > { public: inline FullLut( _FunctionT_ function = _FunctionT_()) : BaseLut<_FunctionT_, _OutputT_, _InputT_, FullLutKey<_InputT_> >( FullLutKey<_InputT_>(), function) { } }; #ifdef _USE_QT_TYPES_ typedef quint8 lut_uint8; typedef quint16 lut_uint16; typedef quint32 lut_uint32; #else #include typedef uint8_t lut_uint8; typedef uint16_t lut_uint16; typedef uint32_t lut_uint32; #endif // integer specialization #define PARTIAL_LUT_INT_SPECIALIZATION(_INT_TYPE_) \ template<> \ class LutKey<_INT_TYPE_> { \ public: \ LutKey<_INT_TYPE_>(_INT_TYPE_ min, _INT_TYPE_ max) : m_min(min), m_max(max) \ { \ } \ public: \ inline int inputToKey(_INT_TYPE_ i) const \ { \ return i - m_min; \ } \ inline _INT_TYPE_ keyToInput(int k) const \ { \ return k + m_min; \ } \ inline bool inrange(_INT_TYPE_ i) const \ { \ return i >= m_min && i <= m_max; \ } \ inline _INT_TYPE_ minimum() const \ { \ return m_min; \ } \ inline _INT_TYPE_ maximum() const \ { \ return m_max; \ } \ inline int size() const \ { \ return m_max - m_min + 1; \ } \ private: \ _INT_TYPE_ m_min, m_max; \ }; PARTIAL_LUT_INT_SPECIALIZATION(lut_uint8) PARTIAL_LUT_INT_SPECIALIZATION(lut_uint16) PARTIAL_LUT_INT_SPECIALIZATION(lut_uint32) #define FULL_LUT_INT_SPECIALIZATION(_INT_TYPE_, _MIN_, _MAX_) \ template<> \ class FullLutKey<_INT_TYPE_> { \ public: \ FullLutKey<_INT_TYPE_>() \ { \ } \ public: \ inline int inputToKey(_INT_TYPE_ i) const \ { \ return i - _MIN_; \ } \ inline _INT_TYPE_ keyToInput(int k) const \ { \ return k + _MIN_; \ } \ inline bool inrange(_INT_TYPE_ ) const \ { \ return true; \ } \ inline _INT_TYPE_ minimum() const \ { \ return _MIN_; \ } \ inline _INT_TYPE_ maximum() const \ { \ return _MAX_; \ } \ inline int size() const \ { \ return _MAX_ - _MIN_ + 1; \ } \ private: \ }; FULL_LUT_INT_SPECIALIZATION(lut_uint8, 0, 255) FULL_LUT_INT_SPECIALIZATION(lut_uint16, 0, 65535) // float specialization /** * This provide an implementation for a LutKey for floating point input values. * * Based on "High-speed Conversion of Floating Point Images to 8-bit" by Bill Spitzaks - * (http://mysite.verizon.net/spitzak/conversion/) + * (https://spitzak.github.io/conversion/sketches_0265.pdf) */ template<> class LutKey { public: union IFNumber { lut_uint32 i; float f; }; public: LutKey(float min, float max, float precision) : m_min(min), m_max(max), m_precision(precision) { // Those values where computed using the test_linear and setting the shift and then using // the standard deviation. if (precision <= 0.000011809f) { m_min = 1; m_max = -1; } else if (precision <= 0.0000237291f) m_shift = 8; else if (precision <= 0.0000475024f) m_shift = 9; else if (precision <= 0.0000948575f) m_shift = 10; else if (precision <= 0.00019013f) m_shift = 11; else if (precision <= 0.000379523f) m_shift = 12; else if (precision <= 0.000758431f) m_shift = 13; else if (precision <= 0.00151891f) m_shift = 14; else if (precision <= 0.00303725f) m_shift = 15; else m_shift = 16; if ( 0.0 <= m_min && m_min <= precision) m_min = precision; if ( -precision <= m_max && m_max <= 0.0) m_max = -precision; IFNumber uf; if(m_min > 0 && m_max > 0) { uf.f = m_min; m_tMin_p = uf.i >> m_shift; uf.f = m_max; m_tMax_p = uf.i >> m_shift; m_tMin_n = m_tMax_p; m_tMax_n = m_tMax_p; } else if( m_max < 0) { uf.f = m_min; m_tMax_n = uf.i >> m_shift; uf.f = m_max; m_tMin_n = uf.i >> m_shift; m_tMin_p = m_tMax_n; m_tMax_p = m_tMax_n; } else { // m_min <0 && m_max > 0 uf.f = precision; m_tMin_p = uf.i >> m_shift; uf.f = m_max; m_tMax_p = uf.i >> m_shift; uf.f = -precision; m_tMin_n = uf.i >> m_shift; uf.f = m_min; m_tMax_n = uf.i >> m_shift; } m_diff_p = m_tMax_p - m_tMin_p; } public: inline int inputToKey(float i) const { IFNumber uf; uf.f = i; int k = (uf.i >> m_shift); if(k <= m_tMax_p) { return k - m_tMin_p; } else { return k - m_tMin_n + m_diff_p; } } inline float keyToInput(int k) const { IFNumber uf; if( k <= m_diff_p ) { uf.i = ((k + m_tMin_p) << m_shift); } else { uf.i = ((k + m_tMin_n - m_diff_p ) << m_shift); } return uf.f; } inline bool inrange(float i) const { return i >= m_min && i <= m_max && (i < -m_precision || i > m_precision); } inline float minimum() const { return m_min; } inline float maximum() const { return m_max; } inline int size() const { return m_diff_p + m_tMax_n - m_tMin_p + 1; } private: float m_min, m_max, m_precision; int m_tMin_p, m_tMax_p, m_tMin_n, m_tMax_n, m_diff_p; int m_shift; }; #endif diff --git a/libs/pigment/resources/KisSwatch.cpp b/libs/pigment/resources/KisSwatch.cpp index 019638d500..65cd9c14c5 100644 --- a/libs/pigment/resources/KisSwatch.cpp +++ b/libs/pigment/resources/KisSwatch.cpp @@ -1,52 +1,52 @@ /* * This file is part of the KDE project * Copyright (c) 2005 Boudewijn Rempt - * Copyright (c) 2016 L. E. Segovia + * Copyright (c) 2016 L. E. Segovia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; 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 "KisSwatch.h" KisSwatch::KisSwatch(const KoColor &color, const QString &name) : m_color(color) , m_name(name) , m_valid(true) { } void KisSwatch::setName(const QString &name) { m_name = name; m_valid = true; } void KisSwatch::setId(const QString &id) { m_id = id; m_valid = true; } void KisSwatch::setColor(const KoColor &color) { m_color = color; m_valid = true; } void KisSwatch::setSpotColor(bool spotColor) { m_spotColor = spotColor; m_valid = true; } diff --git a/libs/pigment/resources/KisSwatch.h b/libs/pigment/resources/KisSwatch.h index 2507a37b61..25d61c2556 100644 --- a/libs/pigment/resources/KisSwatch.h +++ b/libs/pigment/resources/KisSwatch.h @@ -1,63 +1,63 @@ /* * This file is part of the KDE project * Copyright (c) 2005 Boudewijn Rempt - * Copyright (c) 2016 L. E. Segovia + * Copyright (c) 2016 L. E. Segovia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; 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 KISSWATCH_H #define KISSWATCH_H #include "kritapigment_export.h" #include #include "KoColor.h" class KRITAPIGMENT_EXPORT KisSwatch { public: KisSwatch() = default; KisSwatch(const KoColor &color, const QString &name = QString()); public: QString name() const { return m_name; } void setName(const QString &name); QString id() const { return m_id; } void setId(const QString &id); KoColor color() const { return m_color; } void setColor(const KoColor &color); bool spotColor() const { return m_spotColor; } void setSpotColor(bool spotColor); bool isValid() const { return m_valid; } public: bool operator==(const KisSwatch& rhs) const { return m_color == rhs.m_color && m_name == rhs.m_name; } private: KoColor m_color; QString m_name; QString m_id; bool m_spotColor {false}; bool m_valid {false}; }; #endif // KISSWATCH_H diff --git a/libs/pigment/resources/KisSwatchGroup.cpp b/libs/pigment/resources/KisSwatchGroup.cpp index b8382fb0d0..01fbd4dec5 100644 --- a/libs/pigment/resources/KisSwatchGroup.cpp +++ b/libs/pigment/resources/KisSwatchGroup.cpp @@ -1,208 +1,208 @@ /* This file is part of the KDE project Copyright (c) 2005 Boudewijn Rempt - Copyright (c) 2016 L. E. Segovia + Copyright (c) 2016 L. E. Segovia Copyright (c) 2018 Michael Zhou This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "KisSwatchGroup.h" struct KisSwatchGroup::Private { typedef QMap Column; static int DEFAULT_COLUMN_COUNT; static int DEFAULT_ROW_COUNT; QString name {QString()}; QVector colorMatrix {DEFAULT_COLUMN_COUNT}; int colorCount {0}; int rowCount {DEFAULT_ROW_COUNT}; }; int KisSwatchGroup::Private::DEFAULT_COLUMN_COUNT = 16; int KisSwatchGroup::Private::DEFAULT_ROW_COUNT = 20; KisSwatchGroup::KisSwatchGroup() : d(new Private) { } KisSwatchGroup::~KisSwatchGroup() = default; KisSwatchGroup::KisSwatchGroup(const KisSwatchGroup &rhs) : d(new Private(*rhs.d)) { } KisSwatchGroup &KisSwatchGroup::operator =(const KisSwatchGroup &rhs) { if (&rhs == this) { return *this; } d.reset(new Private(*rhs.d)); return *this; } void KisSwatchGroup::setEntry(const KisSwatch &e, int column, int row) { Q_ASSERT(column < d->colorMatrix.size() && column >= 0 && row >= 0); if (row >= d->rowCount) { setRowCount(row + 1); } if (!checkEntry(column, row)) { d->colorCount++; } d->colorMatrix[column][row] = e; } bool KisSwatchGroup::checkEntry(int column, int row) const { if (row >= d->rowCount) { return false; } if (column >= d->colorMatrix.size()){ return false; } if (column < 0) { return false; } if (!d->colorMatrix[column].contains(row)) { return false; } if (!d->colorMatrix[column][row].isValid()) { return false; } return true; } bool KisSwatchGroup::removeEntry(int column, int row) { if (d->colorCount == 0) { return false; } if (row >= d->rowCount || column >= d->colorMatrix.size() || column < 0) { return false; } // QMap::remove returns 1 if key found else 0 if (d->colorMatrix[column].remove(row)) { d->colorCount -= 1; return true; } else { return false; } } void KisSwatchGroup::setColumnCount(int columnCount) { Q_ASSERT(columnCount >= 0); if (columnCount < d->colorMatrix.size()) { int newColorCount = 0; for (int i = 0; i < columnCount; i++ ) { newColorCount += d->colorMatrix[i].size(); } d->colorCount = newColorCount; } d->colorMatrix.resize(columnCount); } int KisSwatchGroup::columnCount() const { return d->colorMatrix.size(); } KisSwatch KisSwatchGroup::getEntry(int column, int row) const { Q_ASSERT(checkEntry(column, row)); return d->colorMatrix[column][row]; } void KisSwatchGroup::addEntry(const KisSwatch &e) { if (columnCount() == 0) { setColumnCount(Private::DEFAULT_COLUMN_COUNT); } int y = 0; int x = 0; while(checkEntry(x, y)) { if(++x == d->colorMatrix.size()) { x = 0; ++y; } } setEntry(e, x, y); } void KisSwatchGroup::clear() { d->colorMatrix.clear(); } void KisSwatchGroup::setRowCount(int newRowCount) { d->rowCount = newRowCount; for (Private::Column &c : d->colorMatrix) { for (int k : c.keys()) { if (k >= newRowCount) { c.remove(k); d->colorCount--; } } } } int KisSwatchGroup::rowCount() const { return d->rowCount; } int KisSwatchGroup::colorCount() const { return d->colorCount; } QList KisSwatchGroup::infoList() const { QList res; int column = 0; for (const Private::Column &c : d->colorMatrix) { int i = 0; for (const KisSwatch &s : c.values()) { SwatchInfo info = {d->name, s, c.keys()[i++], column}; res.append(info); } column++; } return res; } void KisSwatchGroup::setName(const QString &name) { d->name = name; } QString KisSwatchGroup::name() const { return d->name; } diff --git a/libs/pigment/resources/KisSwatchGroup.h b/libs/pigment/resources/KisSwatchGroup.h index e98515edea..7794938449 100644 --- a/libs/pigment/resources/KisSwatchGroup.h +++ b/libs/pigment/resources/KisSwatchGroup.h @@ -1,127 +1,127 @@ /* This file is part of the KDE project Copyright (c) 2005 Boudewijn Rempt - Copyright (c) 2016 L. E. Segovia + Copyright (c) 2016 L. E. Segovia Copyright (c) 2018 Michael Zhou This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KISSWATCHGROUP_H #define KISSWATCHGROUP_H #include "KisSwatch.h" #include "kritapigment_export.h" #include #include #include #include /** * @brief The KisSwatchGroup class stores a matrix of color swatches * swatches can accessed using (x, y) coordinates. * x is the column number from left to right and y is the row number from top * to bottom. * Both x and y start at 0 * there could be empty entries, so the checkEntry(int, int) method must used * whenever you want to get an entry from the matrix */ class KRITAPIGMENT_EXPORT KisSwatchGroup { public /* struct */: struct SwatchInfo { QString group; KisSwatch swatch; int row; int column; }; public: KisSwatchGroup(); ~KisSwatchGroup(); KisSwatchGroup(const KisSwatchGroup &rhs); KisSwatchGroup &operator =(const KisSwatchGroup &rhs); public /* methods */: void setName(const QString &name); QString name() const; void setColumnCount(int columnCount); int columnCount() const; void setRowCount(int newRowCount); int rowCount() const; int colorCount() const; QList infoList() const; /** * @brief checkEntry * checks if position @p column and @p row has a valid entry * both @p column and @p row start from 0 * @param column * @param row * @return true if there is a valid entry at position (column, row) */ bool checkEntry(int column, int row) const; /** * @brief setEntry * sets the entry at position (@p column, @p row) to be @p e * @param e * @param column * @param row */ void setEntry(const KisSwatch &e, int column, int row); /** * @brief getEntry * used to get the swatch entry at position (@p column, @p row) * there is an assertion to make sure that this position isn't empty, * so checkEntry(int, int) must be used before this method to ensure * a valid entry can be found * @param column * @param row * @return the swatch entry at position (column, row) */ KisSwatch getEntry(int column, int row) const; /** * @brief removeEntry * removes the entry at position (@p column, @p row) * @param column * @param row * @return true if these is an entry at (column, row) */ bool removeEntry(int column, int row); /** * @brief addEntry * adds the entry e to the right of the rightmost entry in the last row * if the rightmost entry in the last row is in the right most column, * add e to the leftmost column of a new row * * when column is set to 0, resize number of columns to default * @param e */ void addEntry(const KisSwatch &e); void clear(); private /* member variables */: struct Private; QScopedPointer d; }; #endif // KISSWATCHGROUP_H diff --git a/libs/pigment/resources/KoColorSet.cpp b/libs/pigment/resources/KoColorSet.cpp index 722f221884..da4dffde8d 100644 --- a/libs/pigment/resources/KoColorSet.cpp +++ b/libs/pigment/resources/KoColorSet.cpp @@ -1,1646 +1,1646 @@ /* This file is part of the KDE project Copyright (c) 2005 Boudewijn Rempt - Copyright (c) 2016 L. E. Segovia + Copyright (c) 2016 L. E. Segovia This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // qFromLittleEndian #include #include #include #include #include #include #include #include #include "KisSwatch.h" #include "KoColorSet.h" #include "KoColorSet_p.h" namespace { /** * readAllLinesSafe() reads all the lines in the byte array * using the automated UTF8 and CR/LF transformations. That * might be necessary for opening GPL palettes created on Linux * in Windows environment. */ QStringList readAllLinesSafe(QByteArray *data) { QStringList lines; QBuffer buffer(data); buffer.open(QBuffer::ReadOnly); QTextStream stream(&buffer); QString line; while (stream.readLineInto(&line)) { lines << line; } return lines; } } const QString KoColorSet::GLOBAL_GROUP_NAME = QString(); const QString KoColorSet::KPL_VERSION_ATTR = "version"; const QString KoColorSet::KPL_GROUP_ROW_COUNT_ATTR = "rows"; const QString KoColorSet::KPL_PALETTE_COLUMN_COUNT_ATTR = "columns"; const QString KoColorSet::KPL_PALETTE_NAME_ATTR = "name"; const QString KoColorSet::KPL_PALETTE_COMMENT_ATTR = "comment"; const QString KoColorSet::KPL_PALETTE_FILENAME_ATTR = "filename"; const QString KoColorSet::KPL_PALETTE_READONLY_ATTR = "readonly"; const QString KoColorSet::KPL_COLOR_MODEL_ID_ATTR = "colorModelId"; const QString KoColorSet::KPL_COLOR_DEPTH_ID_ATTR = "colorDepthId"; const QString KoColorSet::KPL_GROUP_NAME_ATTR = "name"; const QString KoColorSet::KPL_SWATCH_ROW_ATTR = "row"; const QString KoColorSet::KPL_SWATCH_COL_ATTR = "column"; const QString KoColorSet::KPL_SWATCH_NAME_ATTR = "name"; const QString KoColorSet::KPL_SWATCH_ID_ATTR = "id"; const QString KoColorSet::KPL_SWATCH_SPOT_ATTR = "spot"; const QString KoColorSet::KPL_SWATCH_BITDEPTH_ATTR = "bitdepth"; const QString KoColorSet::KPL_PALETTE_PROFILE_TAG = "Profile"; const QString KoColorSet::KPL_SWATCH_POS_TAG = "Position"; const QString KoColorSet::KPL_SWATCH_TAG = "ColorSetEntry"; const QString KoColorSet::KPL_GROUP_TAG = "Group"; const QString KoColorSet::KPL_PALETTE_TAG = "ColorSet"; const int MAXIMUM_ALLOWED_COLUMNS = 4096; KoColorSet::KoColorSet(const QString& filename) : KoResource(filename) , d(new Private(this)) { if (!filename.isEmpty()) { QFileInfo f(filename); setIsEditable(f.isWritable()); } } /// Create an copied palette KoColorSet::KoColorSet(const KoColorSet& rhs) : QObject(0) , KoResource(rhs) , d(new Private(this)) { d->paletteType = rhs.d->paletteType; d->data = rhs.d->data; d->comment = rhs.d->comment; d->groupNames = rhs.d->groupNames; d->groups = rhs.d->groups; d->isGlobal = rhs.d->isGlobal; d->isEditable = rhs.d->isEditable; } KoColorSet::~KoColorSet() { } bool KoColorSet::load() { QFile file(filename()); if (file.size() == 0) return false; if (!file.open(QIODevice::ReadOnly)) { warnPigment << "Can't open file " << filename(); return false; } bool res = loadFromDevice(&file); file.close(); if (!QFileInfo(filename()).isWritable()) { setIsEditable(false); } return res; } bool KoColorSet::loadFromDevice(QIODevice *dev) { if (!dev->isOpen()) dev->open(QIODevice::ReadOnly); d->data = dev->readAll(); Q_ASSERT(d->data.size() != 0); return d->init(); } bool KoColorSet::save() { if (d->isGlobal) { // save to resource dir QFile file(filename()); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return false; } saveToDevice(&file); file.close(); return true; } else { return true; // palette is not global, but still indicate that it's saved } } bool KoColorSet::saveToDevice(QIODevice *dev) const { bool res; switch(d->paletteType) { case GPL: res = d->saveGpl(dev); break; default: res = d->saveKpl(dev); } if (res) { KoResource::saveToDevice(dev); } return res; } QByteArray KoColorSet::toByteArray() const { QBuffer s; s.open(QIODevice::WriteOnly); if (!saveToDevice(&s)) { warnPigment << "saving palette failed:" << name(); return QByteArray(); } s.close(); s.open(QIODevice::ReadOnly); QByteArray res = s.readAll(); s.close(); return res; } bool KoColorSet::fromByteArray(QByteArray &data) { QBuffer buf(&data); buf.open(QIODevice::ReadOnly); return loadFromDevice(&buf); } KoColorSet::PaletteType KoColorSet::paletteType() const { return d->paletteType; } void KoColorSet::setPaletteType(PaletteType paletteType) { d->paletteType = paletteType; QString suffix; switch(d->paletteType) { case GPL: suffix = ".gpl"; break; case ACT: suffix = ".act"; break; case RIFF_PAL: case PSP_PAL: suffix = ".pal"; break; case ACO: suffix = ".aco"; break; case XML: suffix = ".xml"; break; case KPL: suffix = ".kpl"; break; case SBZ: suffix = ".sbz"; break; default: suffix = defaultFileExtension(); } QStringList fileName = filename().split("."); fileName.last() = suffix.replace(".", ""); setFilename(fileName.join(".")); } quint32 KoColorSet::colorCount() const { int colorCount = 0; for (KisSwatchGroup &g : d->groups.values()) { colorCount += g.colorCount(); } return colorCount; } void KoColorSet::add(const KisSwatch &c, const QString &groupName) { KisSwatchGroup &modifiedGroup = d->groups.contains(groupName) ? d->groups[groupName] : d->global(); modifiedGroup.addEntry(c); } void KoColorSet::setEntry(const KisSwatch &e, int x, int y, const QString &groupName) { KisSwatchGroup &modifiedGroup = d->groups.contains(groupName) ? d->groups[groupName] : d->global(); modifiedGroup.setEntry(e, x, y); } void KoColorSet::clear() { d->groups.clear(); d->groupNames.clear(); d->groups[GLOBAL_GROUP_NAME] = KisSwatchGroup(); d->groupNames.append(GLOBAL_GROUP_NAME); } KisSwatch KoColorSet::getColorGlobal(quint32 x, quint32 y) const { for (const QString &groupName : getGroupNames()) { if (d->groups.contains(groupName)) { if ((int)y < d->groups[groupName].rowCount()) { return d->groups[groupName].getEntry(x, y); } else { y -= d->groups[groupName].rowCount(); } } } return KisSwatch(); } KisSwatch KoColorSet::getColorGroup(quint32 x, quint32 y, QString groupName) { KisSwatch e; const KisSwatchGroup &sourceGroup = groupName == QString() ? d->global() : d->groups[groupName]; if (sourceGroup.checkEntry(x, y)) { e = sourceGroup.getEntry(x, y); } return e; } QStringList KoColorSet::getGroupNames() const { if (d->groupNames.size() != d->groups.size()) { warnPigment << "mismatch between groups and the groupnames list."; return QStringList(d->groups.keys()); } return d->groupNames; } bool KoColorSet::changeGroupName(const QString &oldGroupName, const QString &newGroupName) { if (!d->groups.contains(oldGroupName)) { return false; } if (oldGroupName == newGroupName) { return true; } d->groups[newGroupName] = d->groups[oldGroupName]; d->groups.remove(oldGroupName); d->groups[newGroupName].setName(newGroupName); //rename the string in the stringlist; int index = d->groupNames.indexOf(oldGroupName); d->groupNames.replace(index, newGroupName); return true; } void KoColorSet::setColumnCount(int columns) { d->groups[GLOBAL_GROUP_NAME].setColumnCount(columns); for (KisSwatchGroup &g : d->groups.values()) { g.setColumnCount(columns); } } int KoColorSet::columnCount() const { return d->groups[GLOBAL_GROUP_NAME].columnCount(); } QString KoColorSet::comment() { return d->comment; } void KoColorSet::setComment(QString comment) { d->comment = comment; } bool KoColorSet::addGroup(const QString &groupName) { if (d->groups.contains(groupName) || getGroupNames().contains(groupName)) { return false; } d->groupNames.append(groupName); d->groups[groupName] = KisSwatchGroup(); d->groups[groupName].setName(groupName); return true; } bool KoColorSet::moveGroup(const QString &groupName, const QString &groupNameInsertBefore) { if (!d->groupNames.contains(groupName) || d->groupNames.contains(groupNameInsertBefore)==false) { return false; } if (groupNameInsertBefore != GLOBAL_GROUP_NAME && groupName != GLOBAL_GROUP_NAME) { d->groupNames.removeAt(d->groupNames.indexOf(groupName)); int index = d->groupNames.indexOf(groupNameInsertBefore); d->groupNames.insert(index, groupName); } return true; } bool KoColorSet::removeGroup(const QString &groupName, bool keepColors) { if (!d->groups.contains(groupName)) { return false; } if (groupName == GLOBAL_GROUP_NAME) { return false; } if (keepColors) { // put all colors directly below global int startingRow = d->groups[GLOBAL_GROUP_NAME].rowCount(); for (const KisSwatchGroup::SwatchInfo &info : d->groups[groupName].infoList()) { d->groups[GLOBAL_GROUP_NAME].setEntry(info.swatch, info.column, info.row + startingRow); } } d->groupNames.removeAt(d->groupNames.indexOf(groupName)); d->groups.remove(groupName); return true; } QString KoColorSet::defaultFileExtension() const { return QString(".kpl"); } int KoColorSet::rowCount() const { int res = 0; for (const QString &name : getGroupNames()) { res += d->groups[name].rowCount(); } return res; } KisSwatchGroup *KoColorSet::getGroup(const QString &name) { if (!d->groups.contains(name)) { return 0; } return &(d->groups[name]); } KisSwatchGroup *KoColorSet::getGlobalGroup() { return getGroup(GLOBAL_GROUP_NAME); } bool KoColorSet::isGlobal() const { return d->isGlobal; } void KoColorSet::setIsGlobal(bool isGlobal) { d->isGlobal = isGlobal; } bool KoColorSet::isEditable() const { return d->isEditable; } void KoColorSet::setIsEditable(bool isEditable) { d->isEditable = isEditable; } KisSwatchGroup::SwatchInfo KoColorSet::getClosestColorInfo(KoColor compare, bool useGivenColorSpace) { KisSwatchGroup::SwatchInfo res; quint8 highestPercentage = 0; quint8 testPercentage = 0; for (const QString &groupName : getGroupNames()) { KisSwatchGroup *group = getGroup(groupName); for (const KisSwatchGroup::SwatchInfo &currInfo : group->infoList()) { KoColor color = currInfo.swatch.color(); if (useGivenColorSpace == true && compare.colorSpace() != color.colorSpace()) { color.convertTo(compare.colorSpace()); } else if (compare.colorSpace() != color.colorSpace()) { compare.convertTo(color.colorSpace()); } testPercentage = (255 - compare.colorSpace()->difference(compare.data(), color.data())); if (testPercentage > highestPercentage) { highestPercentage = testPercentage; res = currInfo; } } } return res; } /********************************KoColorSet::Private**************************/ KoColorSet::Private::Private(KoColorSet *a_colorSet) : colorSet(a_colorSet) { groups[KoColorSet::GLOBAL_GROUP_NAME] = KisSwatchGroup(); groupNames.append(KoColorSet::GLOBAL_GROUP_NAME); } KoColorSet::PaletteType KoColorSet::Private::detectFormat(const QString &fileName, const QByteArray &ba) { QFileInfo fi(fileName); // .pal if (ba.startsWith("RIFF") && ba.indexOf("PAL data", 8)) { return KoColorSet::RIFF_PAL; } // .gpl else if (ba.startsWith("GIMP Palette")) { return KoColorSet::GPL; } // .pal else if (ba.startsWith("JASC-PAL")) { return KoColorSet::PSP_PAL; } else if (fi.suffix().toLower() == "aco") { return KoColorSet::ACO; } else if (fi.suffix().toLower() == "act") { return KoColorSet::ACT; } else if (fi.suffix().toLower() == "xml") { return KoColorSet::XML; } else if (fi.suffix().toLower() == "kpl") { return KoColorSet::KPL; } else if (fi.suffix().toLower() == "sbz") { return KoColorSet::SBZ; } return KoColorSet::UNKNOWN; } void KoColorSet::Private::scribusParseColor(KoColorSet *set, QXmlStreamReader *xml) { KisSwatch colorEntry; // It's a color, retrieve it QXmlStreamAttributes colorProperties = xml->attributes(); QStringRef colorName = colorProperties.value("NAME"); colorEntry.setName(colorName.isEmpty() || colorName.isNull() ? i18n("Untitled") : colorName.toString()); // RGB or CMYK? if (colorProperties.hasAttribute("RGB")) { dbgPigment << "Color " << colorProperties.value("NAME") << ", RGB " << colorProperties.value("RGB"); KoColor currentColor(KoColorSpaceRegistry::instance()->rgb8()); QStringRef colorValue = colorProperties.value("RGB"); if (colorValue.length() != 7 && colorValue.at(0) != '#') { // Color is a hexadecimal number xml->raiseError("Invalid rgb8 color (malformed): " + colorValue); return; } else { bool rgbOk; quint32 rgb = colorValue.mid(1).toUInt(&rgbOk, 16); if (!rgbOk) { xml->raiseError("Invalid rgb8 color (unable to convert): " + colorValue); return; } quint8 r = rgb >> 16 & 0xff; quint8 g = rgb >> 8 & 0xff; quint8 b = rgb & 0xff; dbgPigment << "Color parsed: "<< r << g << b; currentColor.data()[0] = r; currentColor.data()[1] = g; currentColor.data()[2] = b; currentColor.setOpacity(OPACITY_OPAQUE_U8); colorEntry.setColor(currentColor); set->add(colorEntry); while(xml->readNextStartElement()) { //ignore - these are all unknown or the /> element tag xml->skipCurrentElement(); } return; } } else if (colorProperties.hasAttribute("CMYK")) { dbgPigment << "Color " << colorProperties.value("NAME") << ", CMYK " << colorProperties.value("CMYK"); KoColor currentColor(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id(), QString())); QStringRef colorValue = colorProperties.value("CMYK"); if (colorValue.length() != 9 && colorValue.at(0) != '#') { // Color is a hexadecimal number xml->raiseError("Invalid cmyk color (malformed): " % colorValue); return; } else { bool cmykOk; quint32 cmyk = colorValue.mid(1).toUInt(&cmykOk, 16); // cmyk uses the full 32 bits if (!cmykOk) { xml->raiseError("Invalid cmyk color (unable to convert): " % colorValue); return; } quint8 c = cmyk >> 24 & 0xff; quint8 m = cmyk >> 16 & 0xff; quint8 y = cmyk >> 8 & 0xff; quint8 k = cmyk & 0xff; dbgPigment << "Color parsed: "<< c << m << y << k; currentColor.data()[0] = c; currentColor.data()[1] = m; currentColor.data()[2] = y; currentColor.data()[3] = k; currentColor.setOpacity(OPACITY_OPAQUE_U8); colorEntry.setColor(currentColor); set->add(colorEntry); while(xml->readNextStartElement()) { //ignore - these are all unknown or the /> element tag xml->skipCurrentElement(); } return; } } else { xml->raiseError("Unknown color space for color " + colorEntry.name()); } } bool KoColorSet::Private::loadScribusXmlPalette(KoColorSet *set, QXmlStreamReader *xml) { //1. Get name QXmlStreamAttributes paletteProperties = xml->attributes(); QStringRef paletteName = paletteProperties.value("Name"); dbgPigment << "Processed name of palette:" << paletteName; set->setName(paletteName.toString()); //2. Inside the SCRIBUSCOLORS, there are lots of colors. Retrieve them while(xml->readNextStartElement()) { QStringRef currentElement = xml->name(); if(QStringRef::compare(currentElement, "COLOR", Qt::CaseInsensitive) == 0) { scribusParseColor(set, xml); } else { xml->skipCurrentElement(); } } if(xml->hasError()) { return false; } return true; } quint16 KoColorSet::Private::readShort(QIODevice *io) { quint16 val; quint64 read = io->read((char*)&val, 2); if (read != 2) return false; return qFromBigEndian(val); } bool KoColorSet::Private::init() { // just in case this is a reload (eg by KoEditColorSetDialog), groupNames.clear(); groups.clear(); groupNames.append(KoColorSet::GLOBAL_GROUP_NAME); groups[KoColorSet::GLOBAL_GROUP_NAME] = KisSwatchGroup(); if (colorSet->filename().isNull()) { warnPigment << "Cannot load palette" << colorSet->name() << "there is no filename set"; return false; } if (data.isNull()) { QFile file(colorSet->filename()); if (file.size() == 0) { warnPigment << "Cannot load palette" << colorSet->name() << "there is no data available"; return false; } file.open(QIODevice::ReadOnly); data = file.readAll(); file.close(); } bool res = false; paletteType = detectFormat(colorSet->filename(), data); switch(paletteType) { case GPL: res = loadGpl(); break; case ACT: res = loadAct(); break; case RIFF_PAL: res = loadRiff(); break; case PSP_PAL: res = loadPsp(); break; case ACO: res = loadAco(); break; case XML: res = loadXml(); break; case KPL: res = loadKpl(); break; case SBZ: res = loadSbz(); break; default: res = false; } colorSet->setValid(res); QImage img(global().columnCount() * 4, global().rowCount() * 4, QImage::Format_ARGB32); QPainter gc(&img); gc.fillRect(img.rect(), Qt::darkGray); for (const KisSwatchGroup::SwatchInfo &info : global().infoList()) { QColor c = info.swatch.color().toQColor(); gc.fillRect(info.column * 4, info.row * 4, 4, 4, c); } colorSet->setImage(img); colorSet->setValid(res); data.clear(); return res; } bool KoColorSet::Private::saveGpl(QIODevice *dev) const { Q_ASSERT(dev->isOpen()); Q_ASSERT(dev->isWritable()); QTextStream stream(dev); stream << "GIMP Palette\nName: " << colorSet->name() << "\nColumns: " << colorSet->columnCount() << "\n#\n"; /* * Qt doesn't provide an interface to get a const reference to a QHash, that is * the underlying data structure of groups. Therefore, directly use * groups[KoColorSet::GLOBAL_GROUP_NAME] so that saveGpl can stay const */ for (int y = 0; y < groups[KoColorSet::GLOBAL_GROUP_NAME].rowCount(); y++) { for (int x = 0; x < colorSet->columnCount(); x++) { if (!groups[KoColorSet::GLOBAL_GROUP_NAME].checkEntry(x, y)) { continue; } const KisSwatch& entry = groups[KoColorSet::GLOBAL_GROUP_NAME].getEntry(x, y); QColor c = entry.color().toQColor(); stream << c.red() << " " << c.green() << " " << c.blue() << "\t"; if (entry.name().isEmpty()) stream << "Untitled\n"; else stream << entry.name() << "\n"; } } return true; } bool KoColorSet::Private::loadGpl() { if (data.isEmpty() || data.isNull() || data.length() < 50) { warnPigment << "Illegal Gimp palette file: " << colorSet->filename(); return false; } quint32 index = 0; QStringList lines = readAllLinesSafe(&data); if (lines.size() < 3) { warnPigment << "Not enough lines in palette file: " << colorSet->filename(); return false; } QString columnsText; qint32 r, g, b; KisSwatch e; // Read name if (!lines[0].startsWith("GIMP") || !lines[1].toLower().contains("name")) { warnPigment << "Illegal Gimp palette file: " << colorSet->filename(); return false; } colorSet->setName(i18n(lines[1].split(":")[1].trimmed().toLatin1())); index = 2; // Read columns int columns = 0; if (lines[index].toLower().contains("columns")) { columnsText = lines[index].split(":")[1].trimmed(); columns = columnsText.toInt(); if (columns > MAXIMUM_ALLOWED_COLUMNS) { warnPigment << "Refusing to set unreasonable number of columns (" << columns << ") in GIMP Palette file " << colorSet->filename() << " - using maximum number of allowed columns instead"; global().setColumnCount(MAXIMUM_ALLOWED_COLUMNS); } else { global().setColumnCount(columns); } index = 3; } for (qint32 i = index; i < lines.size(); i++) { if (lines[i].startsWith('#')) { comment += lines[i].mid(1).trimmed() + ' '; } else if (!lines[i].isEmpty()) { QStringList a = lines[i].replace('\t', ' ').split(' ', QString::SkipEmptyParts); if (a.count() < 3) { continue; } r = qBound(0, a[0].toInt(), 255); g = qBound(0, a[1].toInt(), 255); b = qBound(0, a[2].toInt(), 255); e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); for (int i = 0; i != 3; i++) { a.pop_front(); } QString name = a.join(" "); e.setName(name.isEmpty() || name == "Untitled" ? i18n("Untitled") : name); global().addEntry(e); } } int rowCount = global().colorCount()/ global().columnCount(); if (global().colorCount() % global().columnCount()>0) { rowCount ++; } global().setRowCount(rowCount); return true; } bool KoColorSet::Private::loadAct() { QFileInfo info(colorSet->filename()); colorSet->setName(info.completeBaseName()); KisSwatch e; for (int i = 0; i < data.size(); i += 3) { quint8 r = data[i]; quint8 g = data[i+1]; quint8 b = data[i+2]; e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); global().addEntry(e); } return true; } bool KoColorSet::Private::loadRiff() { - // http://worms2d.info/Palette_file + // https://worms2d.info/Palette_file QFileInfo info(colorSet->filename()); colorSet->setName(info.completeBaseName()); KisSwatch e; RiffHeader header; memcpy(&header, data.constData(), sizeof(RiffHeader)); header.colorcount = qFromBigEndian(header.colorcount); for (int i = sizeof(RiffHeader); (i < (int)(sizeof(RiffHeader) + header.colorcount) && i < data.size()); i += 4) { quint8 r = data[i]; quint8 g = data[i+1]; quint8 b = data[i+2]; e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e); } return true; } bool KoColorSet::Private::loadPsp() { QFileInfo info(colorSet->filename()); colorSet->setName(info.completeBaseName()); KisSwatch e; qint32 r, g, b; QStringList l = readAllLinesSafe(&data); if (l.size() < 4) return false; if (l[0] != "JASC-PAL") return false; if (l[1] != "0100") return false; int entries = l[2].toInt(); for (int i = 0; i < entries; ++i) { QStringList a = l[i + 3].replace('\t', ' ').split(' ', QString::SkipEmptyParts); if (a.count() != 3) { continue; } r = qBound(0, a[0].toInt(), 255); g = qBound(0, a[1].toInt(), 255); b = qBound(0, a[2].toInt(), 255); e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); QString name = a.join(" "); e.setName(name.isEmpty() ? i18n("Untitled") : name); groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e); } return true; } bool KoColorSet::Private::loadKpl() { QBuffer buf(&data); buf.open(QBuffer::ReadOnly); QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "krita/x-colorset", KoStore::Zip)); if (!store || store->bad()) { return false; } if (store->hasFile("profiles.xml")) { if (!store->open("profiles.xml")) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); QDomDocument doc; doc.setContent(ba); QDomElement e = doc.documentElement(); QDomElement c = e.firstChildElement(KPL_PALETTE_PROFILE_TAG); while (!c.isNull()) { QString name = c.attribute(KPL_PALETTE_NAME_ATTR); QString filename = c.attribute(KPL_PALETTE_FILENAME_ATTR); QString colorModelId = c.attribute(KPL_COLOR_MODEL_ID_ATTR); QString colorDepthId = c.attribute(KPL_COLOR_DEPTH_ID_ATTR); if (!KoColorSpaceRegistry::instance()->profileByName(name)) { store->open(filename); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(colorModelId, colorDepthId, data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); } } c = c.nextSiblingElement(); } } { if (!store->open("colorset.xml")) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); int desiredColumnCount; QDomDocument doc; doc.setContent(ba); QDomElement e = doc.documentElement(); colorSet->setName(e.attribute(KPL_PALETTE_NAME_ATTR)); colorSet->setIsEditable(e.attribute(KPL_PALETTE_READONLY_ATTR) != "true"); comment = e.attribute(KPL_PALETTE_COMMENT_ATTR); desiredColumnCount = e.attribute(KPL_PALETTE_COLUMN_COUNT_ATTR).toInt(); if (desiredColumnCount > MAXIMUM_ALLOWED_COLUMNS) { warnPigment << "Refusing to set unreasonable number of columns (" << desiredColumnCount << ") in KPL palette file " << colorSet->filename() << " - setting maximum allowed column count instead."; colorSet->setColumnCount(MAXIMUM_ALLOWED_COLUMNS); } else { colorSet->setColumnCount(desiredColumnCount); } loadKplGroup(doc, e, colorSet->getGlobalGroup()); QDomElement g = e.firstChildElement(KPL_GROUP_TAG); while (!g.isNull()) { QString groupName = g.attribute(KPL_GROUP_NAME_ATTR); colorSet->addGroup(groupName); loadKplGroup(doc, g, colorSet->getGroup(groupName)); g = g.nextSiblingElement(KPL_GROUP_TAG); } } buf.close(); return true; } bool KoColorSet::Private::loadAco() { QFileInfo info(colorSet->filename()); colorSet->setName(info.completeBaseName()); QBuffer buf(&data); buf.open(QBuffer::ReadOnly); quint16 version = readShort(&buf); quint16 numColors = readShort(&buf); KisSwatch e; if (version == 1 && buf.size() > 4+numColors*10) { buf.seek(4+numColors*10); version = readShort(&buf); numColors = readShort(&buf); } const quint16 quint16_MAX = 65535; for (int i = 0; i < numColors && !buf.atEnd(); ++i) { quint16 colorSpace = readShort(&buf); quint16 ch1 = readShort(&buf); quint16 ch2 = readShort(&buf); quint16 ch3 = readShort(&buf); quint16 ch4 = readShort(&buf); bool skip = false; if (colorSpace == 0) { // RGB const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile(); KoColor c(KoColorSpaceRegistry::instance()->rgb16(srgb)); reinterpret_cast(c.data())[0] = ch3; reinterpret_cast(c.data())[1] = ch2; reinterpret_cast(c.data())[2] = ch1; c.setOpacity(OPACITY_OPAQUE_U8); e.setColor(c); } else if (colorSpace == 1) { // HSB QColor qc; qc.setHsvF(ch1 / 65536.0, ch2 / 65536.0, ch3 / 65536.0); KoColor c(qc, KoColorSpaceRegistry::instance()->rgb16()); c.setOpacity(OPACITY_OPAQUE_U8); e.setColor(c); } else if (colorSpace == 2) { // CMYK KoColor c(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id(), QString())); reinterpret_cast(c.data())[0] = quint16_MAX - ch1; reinterpret_cast(c.data())[1] = quint16_MAX - ch2; reinterpret_cast(c.data())[2] = quint16_MAX - ch3; reinterpret_cast(c.data())[3] = quint16_MAX - ch4; c.setOpacity(OPACITY_OPAQUE_U8); e.setColor(c); } else if (colorSpace == 7) { // LAB KoColor c = KoColor(KoColorSpaceRegistry::instance()->lab16()); reinterpret_cast(c.data())[0] = ch3; reinterpret_cast(c.data())[1] = ch2; reinterpret_cast(c.data())[2] = ch1; c.setOpacity(OPACITY_OPAQUE_U8); e.setColor(c); } else if (colorSpace == 8) { // GRAY KoColor c(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), QString())); reinterpret_cast(c.data())[0] = ch1 * (quint16_MAX / 10000); c.setOpacity(OPACITY_OPAQUE_U8); e.setColor(c); } else { warnPigment << "Unsupported colorspace in palette" << colorSet->filename() << "(" << colorSpace << ")"; skip = true; } if (version == 2) { quint16 v2 = readShort(&buf); //this isn't a version, it's a marker and needs to be skipped. Q_UNUSED(v2); quint16 size = readShort(&buf) -1; //then comes the length if (size>0) { QByteArray ba = buf.read(size*2); if (ba.size() == size*2) { QTextCodec *Utf16Codec = QTextCodec::codecForName("UTF-16BE"); e.setName(Utf16Codec->toUnicode(ba)); } else { warnPigment << "Version 2 name block is the wrong size" << colorSet->filename(); } } v2 = readShort(&buf); //end marker also needs to be skipped. Q_UNUSED(v2); } if (!skip) { groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e); } } return true; } bool KoColorSet::Private::loadSbz() { QBuffer buf(&data); buf.open(QBuffer::ReadOnly); // &buf is a subclass of QIODevice QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "application/x-swatchbook", KoStore::Zip)); if (!store || store->bad()) return false; if (store->hasFile("swatchbook.xml")) { // Try opening... if (!store->open("swatchbook.xml")) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); dbgPigment << "XML palette: " << colorSet->filename() << ", SwatchBooker format"; QDomDocument doc; int errorLine, errorColumn; QString errorMessage; bool status = doc.setContent(ba, &errorMessage, &errorLine, &errorColumn); if (!status) { warnPigment << "Illegal XML palette:" << colorSet->filename(); warnPigment << "Error (line" << errorLine << ", column" << errorColumn << "):" << errorMessage; return false; } QDomElement e = doc.documentElement(); // SwatchBook // Start reading properties... QDomElement metadata = e.firstChildElement("metadata"); if (e.isNull()) { warnPigment << "Palette metadata not found"; return false; } QDomElement title = metadata.firstChildElement("dc:title"); QString colorName = title.text(); colorName = colorName.isEmpty() ? i18n("Untitled") : colorName; colorSet->setName(colorName); dbgPigment << "Processed name of palette:" << colorSet->name(); // End reading properties // Now read colors... QDomElement materials = e.firstChildElement("materials"); if (materials.isNull()) { warnPigment << "Materials (color definitions) not found"; return false; } // This one has lots of "color" elements QDomElement colorElement = materials.firstChildElement("color"); if (colorElement.isNull()) { warnPigment << "Color definitions not found (line" << materials.lineNumber() << ", column" << materials.columnNumber() << ")"; return false; } // Also read the swatch book... QDomElement book = e.firstChildElement("book"); if (book.isNull()) { warnPigment << "Palette book (swatch composition) not found (line" << e.lineNumber() << ", column" << e.columnNumber() << ")"; return false; } // Which has lots of "swatch"es (todo: support groups) QDomElement swatch = book.firstChildElement(); if (swatch.isNull()) { warnPigment << "Swatches/groups definition not found (line" << book.lineNumber() << ", column" << book.columnNumber() << ")"; return false; } // We'll store colors here, and as we process swatches // we'll add them to the palette QHash materialsBook; QHash fileColorSpaces; // Color processing for(; !colorElement.isNull(); colorElement = colorElement.nextSiblingElement("color")) { KisSwatch currentEntry; // Set if color is spot currentEntry.setSpotColor(colorElement.attribute("usage") == "spot"); // inside contains id and name // one or more define the color QDomElement currentColorMetadata = colorElement.firstChildElement("metadata"); QDomNodeList currentColorValues = colorElement.elementsByTagName("values"); // Get color name QDomElement colorTitle = currentColorMetadata.firstChildElement("dc:title"); QDomElement colorId = currentColorMetadata.firstChildElement("dc:identifier"); // Is there an id? (we need that at the very least for identifying a color) if (colorId.text().isEmpty()) { warnPigment << "Unidentified color (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")"; return false; } if (materialsBook.contains(colorId.text())) { warnPigment << "Duplicated color definition (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")"; return false; } // Get a valid color name currentEntry.setId(colorId.text()); currentEntry.setName(colorTitle.text().isEmpty() ? colorId.text() : colorTitle.text()); // Get a valid color definition if (currentColorValues.isEmpty()) { warnPigment << "Color definitions not found (line" << colorElement.lineNumber() << ", column" << colorElement.columnNumber() << ")"; return false; } bool firstDefinition = false; const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile(); // Priority: Lab, otherwise the first definition found for(int j = 0; j < currentColorValues.size(); j++) { QDomNode colorValue = currentColorValues.at(j); QDomElement colorValueE = colorValue.toElement(); QString model = colorValueE.attribute("model", QString()); // sRGB,RGB,HSV,HSL,CMY,CMYK,nCLR: 0 -> 1 // YIQ: Y 0 -> 1 : IQ -0.5 -> 0.5 // Lab: L 0 -> 100 : ab -128 -> 127 // XYZ: 0 -> ~100 if (model == "Lab") { QStringList lab = colorValueE.text().split(" "); if (lab.length() != 3) { warnPigment << "Invalid Lab color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float l = lab.at(0).toFloat(&status); float a = lab.at(1).toFloat(&status); float b = lab.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } KoColor c(KoColorSpaceRegistry::instance()->colorSpace(LABAColorModelID.id(), Float32BitsColorDepthID.id(), QString())); reinterpret_cast(c.data())[0] = l; reinterpret_cast(c.data())[1] = a; reinterpret_cast(c.data())[2] = b; c.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; currentEntry.setColor(c); break; // Immediately add this one } else if (model == "sRGB" && !firstDefinition) { QStringList rgb = colorValueE.text().split(" "); if (rgb.length() != 3) { warnPigment << "Invalid sRGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float r = rgb.at(0).toFloat(&status); float g = rgb.at(1).toFloat(&status); float b = rgb.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } KoColor c(KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb)); reinterpret_cast(c.data())[0] = r; reinterpret_cast(c.data())[1] = g; reinterpret_cast(c.data())[2] = b; c.setOpacity(OPACITY_OPAQUE_F); currentEntry.setColor(c); firstDefinition = true; } else if (model == "XYZ" && !firstDefinition) { QStringList xyz = colorValueE.text().split(" "); if (xyz.length() != 3) { warnPigment << "Invalid XYZ color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float x = xyz.at(0).toFloat(&status); float y = xyz.at(1).toFloat(&status); float z = xyz.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } KoColor c(KoColorSpaceRegistry::instance()->colorSpace(XYZAColorModelID.id(), Float32BitsColorDepthID.id(), QString())); reinterpret_cast(c.data())[0] = x; reinterpret_cast(c.data())[1] = y; reinterpret_cast(c.data())[2] = z; c.setOpacity(OPACITY_OPAQUE_F); currentEntry.setColor(c); firstDefinition = true; } // The following color spaces admit an ICC profile (in SwatchBooker) else if (model == "CMYK" && !firstDefinition) { QStringList cmyk = colorValueE.text().split(" "); if (cmyk.length() != 4) { warnPigment << "Invalid CMYK color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float c = cmyk.at(0).toFloat(&status); float m = cmyk.at(1).toFloat(&status); float y = cmyk.at(2).toFloat(&status); float k = cmyk.at(3).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } QString space = colorValueE.attribute("space"); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), QString()); if (!space.isEmpty()) { // Try loading the profile and add it to the registry if (!fileColorSpaces.contains(space)) { store->enterDirectory("profiles"); store->open(space); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile); fileColorSpaces.insert(space, colorSpace); } } else { colorSpace = fileColorSpaces.value(space); } } KoColor color(colorSpace); reinterpret_cast(color.data())[0] = c; reinterpret_cast(color.data())[1] = m; reinterpret_cast(color.data())[2] = y; reinterpret_cast(color.data())[3] = k; color.setOpacity(OPACITY_OPAQUE_F); currentEntry.setColor(color); firstDefinition = true; } else if (model == "GRAY" && !firstDefinition) { QString gray = colorValueE.text(); float g = gray.toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } QString space = colorValueE.attribute("space"); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Float32BitsColorDepthID.id(), QString()); if (!space.isEmpty()) { // Try loading the profile and add it to the registry if (!fileColorSpaces.contains(space)) { store->enterDirectory("profiles"); store->open(space); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile); fileColorSpaces.insert(space, colorSpace); } } else { colorSpace = fileColorSpaces.value(space); } } KoColor c(colorSpace); reinterpret_cast(c.data())[0] = g; c.setOpacity(OPACITY_OPAQUE_F); currentEntry.setColor(c); firstDefinition = true; } else if (model == "RGB" && !firstDefinition) { QStringList rgb = colorValueE.text().split(" "); if (rgb.length() != 3) { warnPigment << "Invalid RGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } float r = rgb.at(0).toFloat(&status); float g = rgb.at(1).toFloat(&status); float b = rgb.at(2).toFloat(&status); if (!status) { warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; } QString space = colorValueE.attribute("space"); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb); if (!space.isEmpty()) { // Try loading the profile and add it to the registry if (!fileColorSpaces.contains(space)) { store->enterDirectory("profiles"); store->open(space); QByteArray data; data.resize(store->size()); data = store->read(store->size()); store->close(); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), data); if (profile && profile->valid()) { KoColorSpaceRegistry::instance()->addProfile(profile); colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), profile); fileColorSpaces.insert(space, colorSpace); } } else { colorSpace = fileColorSpaces.value(space); } } KoColor c(colorSpace); reinterpret_cast(c.data())[0] = r; reinterpret_cast(c.data())[1] = g; reinterpret_cast(c.data())[2] = b; c.setOpacity(OPACITY_OPAQUE_F); currentEntry.setColor(c); firstDefinition = true; } else { warnPigment << "Color space not implemented:" << model << "(line" << colorValueE.lineNumber() << ", column "<< colorValueE.columnNumber() << ")"; } } if (firstDefinition) { materialsBook.insert(currentEntry.id(), currentEntry); } else { warnPigment << "No supported color spaces for the current color (line" << colorElement.lineNumber() << ", column "<< colorElement.columnNumber() << ")"; return false; } } // End colors // Now decide which ones will go into the palette for(;!swatch.isNull(); swatch = swatch.nextSiblingElement()) { QString type = swatch.tagName(); if (type.isEmpty() || type.isNull()) { warnPigment << "Invalid swatch/group definition (no id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; return false; } else if (type == "swatch") { QString id = swatch.attribute("material"); if (id.isEmpty() || id.isNull()) { warnPigment << "Invalid swatch definition (no material id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; return false; } if (materialsBook.contains(id)) { groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(materialsBook.value(id)); } else { warnPigment << "Invalid swatch definition (material not found) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; return false; } } else if (type == "group") { QDomElement groupMetadata = swatch.firstChildElement("metadata"); if (groupMetadata.isNull()) { warnPigment << "Invalid group definition (missing metadata) (line" << groupMetadata.lineNumber() << ", column" << groupMetadata.columnNumber() << ")"; return false; } QDomElement groupTitle = metadata.firstChildElement("dc:title"); if (groupTitle.isNull()) { warnPigment << "Invalid group definition (missing title) (line" << groupTitle.lineNumber() << ", column" << groupTitle.columnNumber() << ")"; return false; } QString currentGroupName = groupTitle.text(); QDomElement groupSwatch = swatch.firstChildElement("swatch"); while(!groupSwatch.isNull()) { QString id = groupSwatch.attribute("material"); if (id.isEmpty() || id.isNull()) { warnPigment << "Invalid swatch definition (no material id) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")"; return false; } if (materialsBook.contains(id)) { groups[currentGroupName].addEntry(materialsBook.value(id)); } else { warnPigment << "Invalid swatch definition (material not found) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")"; return false; } groupSwatch = groupSwatch.nextSiblingElement("swatch"); } } } // End palette } buf.close(); return true; } bool KoColorSet::Private::loadXml() { bool res = false; QXmlStreamReader *xml = new QXmlStreamReader(data); if (xml->readNextStartElement()) { QStringRef paletteId = xml->name(); if (QStringRef::compare(paletteId, "SCRIBUSCOLORS", Qt::CaseInsensitive) == 0) { // Scribus dbgPigment << "XML palette: " << colorSet->filename() << ", Scribus format"; res = loadScribusXmlPalette(colorSet, xml); } else { // Unknown XML format xml->raiseError("Unknown XML palette format. Expected SCRIBUSCOLORS, found " + paletteId); } } // If there is any error (it should be returned through the stream) if (xml->hasError() || !res) { warnPigment << "Illegal XML palette:" << colorSet->filename(); warnPigment << "Error (line"<< xml->lineNumber() << ", column" << xml->columnNumber() << "):" << xml->errorString(); return false; } else { dbgPigment << "XML palette parsed successfully:" << colorSet->filename(); return true; } } bool KoColorSet::Private::saveKpl(QIODevice *dev) const { QScopedPointer store(KoStore::createStore(dev, KoStore::Write, "krita/x-colorset", KoStore::Zip)); if (!store || store->bad()) return false; QSet colorSpaces; { QDomDocument doc; QDomElement root = doc.createElement(KPL_PALETTE_TAG); root.setAttribute(KPL_VERSION_ATTR, "1.0"); root.setAttribute(KPL_PALETTE_NAME_ATTR, colorSet->name()); root.setAttribute(KPL_PALETTE_COMMENT_ATTR, comment); root.setAttribute(KPL_PALETTE_READONLY_ATTR, (colorSet->isEditable() || !colorSet->isGlobal()) ? "false" : "true"); root.setAttribute(KPL_PALETTE_COLUMN_COUNT_ATTR, colorSet->columnCount()); root.setAttribute(KPL_GROUP_ROW_COUNT_ATTR, groups[KoColorSet::GLOBAL_GROUP_NAME].rowCount()); saveKplGroup(doc, root, colorSet->getGroup(KoColorSet::GLOBAL_GROUP_NAME), colorSpaces); for (const QString &groupName : groupNames) { if (groupName == KoColorSet::GLOBAL_GROUP_NAME) { continue; } QDomElement gl = doc.createElement(KPL_GROUP_TAG); gl.setAttribute(KPL_GROUP_NAME_ATTR, groupName); root.appendChild(gl); saveKplGroup(doc, gl, colorSet->getGroup(groupName), colorSpaces); } doc.appendChild(root); if (!store->open("colorset.xml")) { return false; } QByteArray ba = doc.toByteArray(); if (store->write(ba) != ba.size()) { return false; } if (!store->close()) { return false; } } QDomDocument doc; QDomElement profileElement = doc.createElement("Profiles"); for (const KoColorSpace *colorSpace : colorSpaces) { QString fn = QFileInfo(colorSpace->profile()->fileName()).fileName(); if (!store->open(fn)) { return false; } QByteArray profileRawData = colorSpace->profile()->rawData(); if (!store->write(profileRawData)) { return false; } if (!store->close()) { return false; } QDomElement el = doc.createElement(KPL_PALETTE_PROFILE_TAG); el.setAttribute(KPL_PALETTE_FILENAME_ATTR, fn); el.setAttribute(KPL_PALETTE_NAME_ATTR, colorSpace->profile()->name()); el.setAttribute(KPL_COLOR_MODEL_ID_ATTR, colorSpace->colorModelId().id()); el.setAttribute(KPL_COLOR_DEPTH_ID_ATTR, colorSpace->colorDepthId().id()); profileElement.appendChild(el); } doc.appendChild(profileElement); if (!store->open("profiles.xml")) { return false; } QByteArray ba = doc.toByteArray(); if (store->write(ba) != ba.size()) { return false; } if (!store->close()) { return false; } return store->finalize(); } void KoColorSet::Private::saveKplGroup(QDomDocument &doc, QDomElement &groupEle, const KisSwatchGroup *group, QSet &colorSetSet) const { groupEle.setAttribute(KPL_GROUP_ROW_COUNT_ATTR, QString::number(group->rowCount())); for (const SwatchInfoType &info : group->infoList()) { const KoColorProfile *profile = info.swatch.color().colorSpace()->profile(); // Only save non-builtin profiles.= if (!profile->fileName().isEmpty()) { colorSetSet.insert(info.swatch.color().colorSpace()); } QDomElement swatchEle = doc.createElement(KPL_SWATCH_TAG); swatchEle.setAttribute(KPL_SWATCH_NAME_ATTR, info.swatch.name()); swatchEle.setAttribute(KPL_SWATCH_ID_ATTR, info.swatch.id()); swatchEle.setAttribute(KPL_SWATCH_SPOT_ATTR, info.swatch.spotColor() ? "true" : "false"); swatchEle.setAttribute(KPL_SWATCH_BITDEPTH_ATTR, info.swatch.color().colorSpace()->colorDepthId().id()); info.swatch.color().toXML(doc, swatchEle); QDomElement positionEle = doc.createElement(KPL_SWATCH_POS_TAG); positionEle.setAttribute(KPL_SWATCH_ROW_ATTR, info.row); positionEle.setAttribute(KPL_SWATCH_COL_ATTR, info.column); swatchEle.appendChild(positionEle); groupEle.appendChild(swatchEle); } } void KoColorSet::Private::loadKplGroup(const QDomDocument &doc, const QDomElement &parentEle, KisSwatchGroup *group) { Q_UNUSED(doc); if (!parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).isNull()) { group->setRowCount(parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).toInt()); } group->setColumnCount(colorSet->columnCount()); for (QDomElement swatchEle = parentEle.firstChildElement(KPL_SWATCH_TAG); !swatchEle.isNull(); swatchEle = swatchEle.nextSiblingElement(KPL_SWATCH_TAG)) { QString colorDepthId = swatchEle.attribute(KPL_SWATCH_BITDEPTH_ATTR, Integer8BitsColorDepthID.id()); KisSwatch entry; entry.setColor(KoColor::fromXML(swatchEle.firstChildElement(), colorDepthId)); entry.setName(swatchEle.attribute(KPL_SWATCH_NAME_ATTR)); entry.setId(swatchEle.attribute(KPL_SWATCH_ID_ATTR)); entry.setSpotColor(swatchEle.attribute(KPL_SWATCH_SPOT_ATTR, "false") == "true" ? true : false); QDomElement positionEle = swatchEle.firstChildElement(KPL_SWATCH_POS_TAG); if (!positionEle.isNull()) { int rowNumber = positionEle.attribute(KPL_SWATCH_ROW_ATTR).toInt(); int columnNumber = positionEle.attribute(KPL_SWATCH_COL_ATTR).toInt(); if (columnNumber < 0 || columnNumber >= colorSet->columnCount() || rowNumber < 0 ) { warnPigment << "Swatch" << entry.name() << "of palette" << colorSet->name() << "has invalid position."; continue; } group->setEntry(entry, columnNumber, rowNumber); } else { group->addEntry(entry); } } if (parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).isNull() && group->colorCount() > 0 && group->columnCount() > 0 && (group->colorCount() / (group->columnCount()) + 1) < 20) { group->setRowCount((group->colorCount() / group->columnCount()) + 1); } } diff --git a/libs/pigment/resources/KoColorSet.h b/libs/pigment/resources/KoColorSet.h index 700f07c57d..9364040fe5 100644 --- a/libs/pigment/resources/KoColorSet.h +++ b/libs/pigment/resources/KoColorSet.h @@ -1,219 +1,219 @@ /* This file is part of the KDE project Copyright (c) 2005 Boudewijn Rempt - Copyright (c) 2016 L. E. Segovia + Copyright (c) 2016 L. E. Segovia This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KOCOLORSET #define KOCOLORSET #include #include #include #include #include #include "KoColor.h" #include "KisSwatch.h" #include "KisSwatchGroup.h" /** * Also called palette. * Open Gimp, Photoshop or RIFF palette files. This is a straight port * from the Gimp. */ class KRITAPIGMENT_EXPORT KoColorSet : public QObject, public KoResource { Q_OBJECT public: static const QString GLOBAL_GROUP_NAME; static const QString KPL_VERSION_ATTR; static const QString KPL_GROUP_ROW_COUNT_ATTR; static const QString KPL_PALETTE_COLUMN_COUNT_ATTR; static const QString KPL_PALETTE_NAME_ATTR; static const QString KPL_PALETTE_COMMENT_ATTR; static const QString KPL_PALETTE_FILENAME_ATTR; static const QString KPL_PALETTE_READONLY_ATTR; static const QString KPL_COLOR_MODEL_ID_ATTR; static const QString KPL_COLOR_DEPTH_ID_ATTR; static const QString KPL_GROUP_NAME_ATTR; static const QString KPL_SWATCH_ROW_ATTR; static const QString KPL_SWATCH_COL_ATTR; static const QString KPL_SWATCH_NAME_ATTR; static const QString KPL_SWATCH_SPOT_ATTR; static const QString KPL_SWATCH_ID_ATTR; static const QString KPL_SWATCH_BITDEPTH_ATTR; static const QString KPL_PALETTE_PROFILE_TAG; static const QString KPL_SWATCH_POS_TAG; static const QString KPL_SWATCH_TAG; static const QString KPL_GROUP_TAG; static const QString KPL_PALETTE_TAG; public: enum PaletteType { UNKNOWN = 0, GPL, // GIMP RIFF_PAL, // RIFF ACT, // Photoshop binary PSP_PAL, // PaintShop Pro ACO, // Photoshop Swatches XML, // XML palette (Scribus) KPL, // KoColor-based XML palette SBZ // SwatchBooker }; /** * Load a color set from a file. This can be a Gimp * palette, a RIFF palette, a Photoshop palette, * a Krita palette, * a Scribus palette or a SwatchBooker palette. */ explicit KoColorSet(const QString &filename = QString()); // Explicit copy constructor (KoResource copy constructor is private) KoColorSet(const KoColorSet& rhs); public /* overridden methods */: // KoResource ~KoColorSet() override; bool load() override; bool loadFromDevice(QIODevice *dev) override; bool save() override; bool saveToDevice(QIODevice* dev) const override; QString defaultFileExtension() const override; public /* methods */: void setColumnCount(int columns); int columnCount() const; void setComment(QString comment); QString comment(); int rowCount() const; quint32 colorCount() const; PaletteType paletteType() const; void setPaletteType(PaletteType paletteType); /** * @brief isGlobal * A global color set is a set stored in the config directory * Such a color set would be opened every time Krita is launched. * * A non-global color set, on contrary, would be stored in a kra file, * and would only be opened when that file is opened by Krita. * @return @c true if the set is global */ bool isGlobal() const; void setIsGlobal(bool); bool isEditable() const; void setIsEditable(bool isEditable); QByteArray toByteArray() const; bool fromByteArray(QByteArray &data); /** * @brief Add a color to the palette. * @param c the swatch * @param groupName color to add the group to. If empty, it will be added to the unsorted. */ void add(const KisSwatch &, const QString &groupName = GLOBAL_GROUP_NAME); void setEntry(const KisSwatch &e, int x, int y, const QString &groupName = GLOBAL_GROUP_NAME); /** * @brief getColorGlobal * A function for getting a color based on a global index. Useful for iterating through all color entries. * @param x the global x index over the whole palette. * @param y the global y index over the whole palette. * @return the entry. */ KisSwatch getColorGlobal(quint32 x, quint32 y) const; /** * @brief getColorGroup * A function for getting the color from a specific group. * @param x the x index over the group. * @param y the y index over the group. * @param groupName the name of the group, will give unsorted when not defined. * @return the entry */ KisSwatch getColorGroup(quint32 x, quint32 y, QString groupName); /** * @brief getGroupNames * @return returns a list of group names, excluding the unsorted group. */ QStringList getGroupNames() const; /** * @brief getGroup * @param name * @return the group with the name given; global group if no parameter is given * null pointer if not found. */ KisSwatchGroup *getGroup(const QString &name); KisSwatchGroup *getGlobalGroup(); bool changeGroupName(const QString &oldGroupName, const QString &newGroupName); /** * @brief addGroup * Adds a new group. * @param groupName the name of the new group. When not specified, this will fail. * @return whether thegroup was made. */ bool addGroup(const QString &groupName); /** * @brief moveGroup * Move a group in the internal stringlist. * @param groupName the groupname to move. * @param groupNameInsertBefore the groupname to insert before. Empty means it will be added to the end. * @return */ bool moveGroup(const QString &groupName, const QString &groupNameInsertBefore = GLOBAL_GROUP_NAME); /** * @brief removeGroup * Remove a group from the KoColorSet * @param groupName the name of the group you want to remove. * @param keepColors Whether you wish to keep the colorsetentries. These will be added to the unsorted. * @return whether it could find the group to remove. */ bool removeGroup(const QString &groupName, bool keepColors = true); void clear(); /** * @brief getIndexClosestColor * function that matches the color to all colors in the colorset, and returns the index * of the closest match. * @param compare the color you wish to compare. * @param useGivenColorSpace whether to use the color space of the color given * when the two colors' colorspaces don't match. Else it'll use the entry's colorspace. * @return returns the int of the closest match. */ KisSwatchGroup::SwatchInfo getClosestColorInfo(KoColor compare, bool useGivenColorSpace = true); private: class Private; const QScopedPointer d; }; #endif // KOCOLORSET diff --git a/libs/pigment/resources/KoSegmentGradient.cpp b/libs/pigment/resources/KoSegmentGradient.cpp index 16fef332cc..9a063b52c3 100644 --- a/libs/pigment/resources/KoSegmentGradient.cpp +++ b/libs/pigment/resources/KoSegmentGradient.cpp @@ -1,983 +1,984 @@ /* 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)); } } KoAbstractGradient* KoSegmentGradient::clone() const { return new KoSegmentGradient(*this); } bool KoSegmentGradient::load() { QFile file(filename()); if (!file.open(QIODevice::ReadOnly)) { warnPigment << "Can't open file " << filename(); return false; } bool res = loadFromDevice(&file); file.close(); return res; } bool KoSegmentGradient::loadFromDevice(QIODevice *dev) { 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::save() { QFile file(filename()); if (!file.open(QIODevice::WriteOnly)) { return false; } saveToDevice(&file); file.close(); return true; } 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 { - Q_ASSERT(t >= 0 || t <= 1); - Q_ASSERT(!m_segments.empty()); + 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/pigment/tests/CCSGraph.cpp b/libs/pigment/tests/CCSGraph.cpp index 6a574af35e..404508ff02 100644 --- a/libs/pigment/tests/CCSGraph.cpp +++ b/libs/pigment/tests/CCSGraph.cpp @@ -1,119 +1,119 @@ /* * Copyright (c) 2007-2008 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColorConversionSystem.h" #include #include #include struct FriendOfColorSpaceRegistry { static QString toDot() { return KoColorSpaceRegistry::instance()->colorConversionSystem()->toDot(); } static QString bestPathToDot(const QString &srcKey, const QString &dstKey) { return KoColorSpaceRegistry::instance()->colorConversionSystem()->bestPathToDot(srcKey, dstKey); } }; int main(int argc, char** argv) { QCoreApplication app(argc, argv); QCommandLineParser parser; parser.addVersionOption(); parser.addHelpOption(); // Initialize the list of options parser.addOption(QCommandLineOption(QStringList() << QLatin1String("graphs"), i18n("return the list of available graphs"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("graph"), i18n("specify the type of graph (see --graphs to get the full list, the default is full)"), QLatin1String("type"), QLatin1String("full"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("key"), i18n("specify the key of the source color space"), QLatin1String("key"), QString())); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("key"), i18n("specify the key of the destination color space"), QLatin1String("key"), QString())); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("output"), i18n("specify the output (can be ps or dot, the default is ps)"), QLatin1String("type"), QLatin1String("ps"))); parser.addPositionalArgument(QLatin1String("outputfile"), i18n("name of the output file")); parser.process(app); // PORTING SCRIPT: move this to after any parser.addOption if (parser.isSet("graphs")) { // Don't change those lines to use dbgPigment derivatives, they need to be outputted // to stdout not stderr. std::cout << "full : show all the connection on the graph" << std::endl; std::cout << "bestpath : show the best path for a given transformation" << std::endl; exit(EXIT_SUCCESS); } QString graphType = parser.value("graph"); QString outputType = parser.value("output"); if (parser.positionalArguments().count() != 1) { errorPigment << "No output file name specified"; parser.showHelp(); exit(EXIT_FAILURE); } QString outputFileName = parser.positionalArguments()[0]; // Generate the graph QString dot; if (graphType == "full") { dot = FriendOfColorSpaceRegistry::toDot(); } else if (graphType == "bestpath") { QString srcKey = parser.value("src-key"); QString dstKey = parser.value("dst-key"); if (srcKey.isEmpty() || dstKey.isEmpty()) { errorPigment << "src-key and dst-key must be specified for the graph bestpath"; exit(EXIT_FAILURE); } else { dot = FriendOfColorSpaceRegistry::bestPathToDot(srcKey, dstKey); } } else { errorPigment << "Unknown graph type : " << graphType.toLatin1(); exit(EXIT_FAILURE); } if (outputType == "dot") { QFile file(outputFileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) exit(EXIT_FAILURE); QTextStream out(&file); out << dot; } else if (outputType == "ps" || outputType == "svg") { QTemporaryFile file; if (!file.open()) { exit(EXIT_FAILURE); } QTextStream out(&file); out << dot; QString cmd = QString("dot -T%1 %2 -o %3").arg(outputType).arg(file.fileName()).arg(outputFileName); file.close(); if (QProcess::execute(cmd) != 0) { - errorPigment << "An error has occurred when executing : '" << cmd << "' the most likely cause is that 'dot' command is missing, and that you should install graphviz (from http://www.graphviz.org)"; + errorPigment << "An error has occurred when executing : '" << cmd << "' the most likely cause is that 'dot' command is missing, and that you should install graphviz (from https://www.graphviz.org)"; } } else { errorPigment << "Unknown output type : " << outputType; exit(EXIT_FAILURE); } } diff --git a/libs/psd/psd.h b/libs/psd/psd.h index acb2b937e2..ff7efc1d09 100644 --- a/libs/psd/psd.h +++ b/libs/psd/psd.h @@ -1,1169 +1,1169 @@ /* * Copyright (c) 2010 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * Constants and defines taken from gimp and psdparse */ #ifndef PSD_H #define PSD_H #include #include #include #include #include #include #include #include "kritapsd_export.h" class KoPattern; const int MAX_CHANNELS = 56; typedef qint32 Fixed; /* Represents a fixed point implied decimal */ /** * Image color/depth modes */ enum psd_color_mode { Bitmap = 0, Grayscale=1, Indexed=2, RGB=3, CMYK=4, MultiChannel=7, DuoTone=8, Lab=9, Gray16, RGB48, Lab48, CMYK64, DeepMultichannel, Duotone16, COLORMODE_UNKNOWN = 9000 }; /** * Color samplers, apparently distict from PSDColormode */ namespace psd_color_sampler { enum PSDColorSamplers { RGB, HSB, CMYK, PANTONE, // LAB FOCOLTONE, // CMYK TRUMATCH, // CMYK TOYO, // LAB LAB, GRAYSCALE, HKS, // CMYK DIC, // LAB TOTAL_INK, MONITOR_RGB, DUOTONE, OPACITY, ANPA = 3000 // LAB }; } // EFFECTS enum psd_gradient_style { psd_gradient_style_linear, // 'Lnr ' psd_gradient_style_radial, // 'Rdl ' psd_gradient_style_angle, // 'Angl' psd_gradient_style_reflected, // 'Rflc' psd_gradient_style_diamond // 'Dmnd' }; enum psd_color_stop_type { psd_color_stop_type_foreground_color, // 'FrgC' psd_color_stop_type_background_Color, // 'BckC' psd_color_stop_type_user_stop // 'UsrS' }; enum psd_technique_type { psd_technique_softer, psd_technique_precise, psd_technique_slope_limit, }; enum psd_stroke_position { psd_stroke_outside, psd_stroke_inside, psd_stroke_center }; enum psd_fill_type { psd_fill_solid_color, psd_fill_gradient, psd_fill_pattern, }; enum psd_glow_source { psd_glow_center, psd_glow_edge, }; enum psd_bevel_style { psd_bevel_outer_bevel, psd_bevel_inner_bevel, psd_bevel_emboss, psd_bevel_pillow_emboss, psd_bevel_stroke_emboss, }; enum psd_direction { psd_direction_up, psd_direction_down }; enum psd_section_type { psd_other = 0, psd_open_folder, psd_closed_folder, psd_bounding_divider }; // GRADIENT MAP // Each color stop struct psd_gradient_color_stop { qint32 location; // Location of color stop qint32 midpoint; // Midpoint of color stop QColor actual_color; psd_color_stop_type color_stop_type; }; // Each transparency stop struct psd_gradient_transparency_stop { qint32 location; // Location of transparency stop qint32 midpoint; // Midpoint of transparency stop qint8 opacity; // Opacity of transparency stop }; // Gradient settings (Photoshop 6.0) struct psd_layer_gradient_map { bool reverse; // Is gradient reverse bool dithered; // Is gradient dithered qint32 name_length; quint16 *name; // Name of the gradient: Unicode string, padded qint8 number_color_stops; // Number of color stops to follow psd_gradient_color_stop * color_stop; qint8 number_transparency_stops;// Number of transparency stops to follow psd_gradient_transparency_stop * transparency_stop; qint8 expansion_count; // Expansion count ( = 2 for Photoshop 6.0) qint8 interpolation; // Interpolation if length above is non-zero qint8 length; // Length (= 32 for Photoshop 6.0) qint8 mode; // Mode for this gradient qint32 random_number_seed; // Random number seed qint8 showing_transparency_flag;// Flag for showing transparency qint8 using_vector_color_flag;// Flag for using vector color qint32 roughness_factor; // Roughness factor QColor min_color; QColor max_color; QColor lookup_table[256]; }; struct psd_gradient_color { qint32 smoothness; qint32 name_length; quint16 * name; // Name of the gradient: Unicode string, padded qint8 number_color_stops; // Number of color stops to follow psd_gradient_color_stop * color_stop; qint8 number_transparency_stops;// Number of transparency stops to follow psd_gradient_transparency_stop *transparency_stop; }; struct psd_pattern { psd_color_mode color_mode = Bitmap; // The image mode of the file. quint8 height = 0; // Point: vertical, 2 bytes and horizontal, 2 bytes quint8 width = 0; QString name; QString uuid; qint32 version = 0; quint8 top = 0; // Rectangle: top, left, bottom, right quint8 left = 0; quint8 bottom = 0; quint8 right = 0; qint32 max_channel = 0; // Max channels qint32 channel_number = 0; QVector color_table; }; struct psd_layer_effects_context { psd_layer_effects_context() : keep_original(false) { } bool keep_original; }; #define PSD_LOOKUP_TABLE_SIZE 256 -// dsdw, isdw: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_22203 +// dsdw, isdw: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_22203 class KRITAPSD_EXPORT psd_layer_effects_shadow_base { public: psd_layer_effects_shadow_base() : m_invertsSelection(false) , m_edgeHidden(true) , m_effectEnabled(false) , m_blendMode(COMPOSITE_MULT) , m_color(Qt::black) , m_nativeColor(Qt::black) , m_opacity(75) , m_angle(120) , m_useGlobalLight(true) , m_distance(21) , m_spread(0) , m_size(21) , m_antiAliased(0) , m_noise(0) , m_knocksOut(false) , m_fillType(psd_fill_solid_color) , m_technique(psd_technique_softer) , m_range(100) , m_jitter(0) , m_gradient(0) { for(int i = 0; i < PSD_LOOKUP_TABLE_SIZE; ++i) { m_contourLookupTable[i] = i; } } virtual ~psd_layer_effects_shadow_base() { } QPoint calculateOffset(const psd_layer_effects_context *context) const; void setEffectEnabled(bool value) { m_effectEnabled = value; } bool effectEnabled() const { return m_effectEnabled; } QString blendMode() const { return m_blendMode; } QColor color() const { return m_color; } QColor nativeColor() const { return m_nativeColor; } qint32 opacity() const { return m_opacity; } qint32 angle() const { return m_angle; } bool useGlobalLight() const { return m_useGlobalLight; } qint32 distance() const { return m_distance; } qint32 spread() const { return m_spread; } qint32 size() const { return m_size; } const quint8* contourLookupTable() const { return m_contourLookupTable; } bool antiAliased() const { return m_antiAliased; } qint32 noise() const { return m_noise; } bool knocksOut() const { return m_knocksOut; } bool invertsSelection() const { return m_invertsSelection; } bool edgeHidden() const { return m_edgeHidden; } psd_fill_type fillType() const { return m_fillType; } psd_technique_type technique() const { return m_technique; } qint32 range() const { return m_range; } qint32 jitter() const { return m_jitter; } KoAbstractGradientSP gradient() const { return m_gradient; } public: void setBlendMode(QString value) { m_blendMode = value; } void setColor(QColor value) { m_color = value; } void setNativeColor(QColor value) { m_nativeColor = value; } void setOpacity(qint32 value) { m_opacity = value; } void setAngle(qint32 value) { m_angle = value; } void setUseGlobalLight(bool value) { m_useGlobalLight = value; } void setDistance(qint32 value) { m_distance = value; } void setSpread(qint32 value) { m_spread = value; } void setSize(qint32 value) { m_size = value; } void setContourLookupTable(const quint8* value) { memcpy(m_contourLookupTable, value, PSD_LOOKUP_TABLE_SIZE * sizeof(quint8)); } void setAntiAliased(bool value) { m_antiAliased = value; } void setNoise(qint32 value) { m_noise = value; } void setKnocksOut(bool value) { m_knocksOut = value; } void setInvertsSelection(bool value) { m_invertsSelection = value; } void setEdgeHidden(bool value) { m_edgeHidden = value; } void setFillType(psd_fill_type value) { m_fillType = value; } void setTechnique(psd_technique_type value) { m_technique = value; } void setRange(qint32 value) { m_range = value; } void setJitter(qint32 value) { m_jitter = value; } void setGradient(KoAbstractGradientSP value) { m_gradient = value; } virtual void scaleLinearSizes(qreal scale) { m_distance *= scale; m_size *= scale; } private: // internal bool m_invertsSelection; bool m_edgeHidden; private: bool m_effectEnabled; // Effect enabled QString m_blendMode; // already in Krita format! QColor m_color; QColor m_nativeColor; qint32 m_opacity; // Opacity as a percent (0...100) qint32 m_angle; // Angle in degrees bool m_useGlobalLight; // Use this angle in all of the layer effects qint32 m_distance; // Distance in pixels qint32 m_spread; // Intensity as a percent qint32 m_size; // Blur value in pixels quint8 m_contourLookupTable[PSD_LOOKUP_TABLE_SIZE]; bool m_antiAliased; qint32 m_noise; bool m_knocksOut; // for Outer/Inner Glow psd_fill_type m_fillType; psd_technique_type m_technique; qint32 m_range; qint32 m_jitter; KoAbstractGradientSP m_gradient; }; class KRITAPSD_EXPORT psd_layer_effects_shadow_common : public psd_layer_effects_shadow_base { public: /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want // using psd_layer_effects_shadow_base::setBlendMode; // using psd_layer_effects_shadow_base::setColor; // using psd_layer_effects_shadow_base::setOpacity; // using psd_layer_effects_shadow_base::setAngle; // using psd_layer_effects_shadow_base::setUseGlobalLight; // using psd_layer_effects_shadow_base::setDistance; // using psd_layer_effects_shadow_base::setSpread; // using psd_layer_effects_shadow_base::setSize; // using psd_layer_effects_shadow_base::setContourLookupTable; // using psd_layer_effects_shadow_base::setAntiAliased; // using psd_layer_effects_shadow_base::setNoise; }; class KRITAPSD_EXPORT psd_layer_effects_drop_shadow : public psd_layer_effects_shadow_common { public: /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want //using psd_layer_effects_shadow_base::setKnocksOut; }; -// isdw: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_22203 +// isdw: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_22203 class KRITAPSD_EXPORT psd_layer_effects_inner_shadow : public psd_layer_effects_shadow_common { public: psd_layer_effects_inner_shadow() { setKnocksOut(false); setInvertsSelection(true); setEdgeHidden(false); } }; class KRITAPSD_EXPORT psd_layer_effects_glow_common : public psd_layer_effects_shadow_base { public: psd_layer_effects_glow_common() { setKnocksOut(true); setDistance(0); setBlendMode(COMPOSITE_LINEAR_DODGE); setColor(Qt::white); } /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want // using psd_layer_effects_shadow_base::setBlendMode; // using psd_layer_effects_shadow_base::setColor; // using psd_layer_effects_shadow_base::setOpacity; // using psd_layer_effects_shadow_base::setSpread; // using psd_layer_effects_shadow_base::setSize; // using psd_layer_effects_shadow_base::setContourLookupTable; // using psd_layer_effects_shadow_base::setAntiAliased; // using psd_layer_effects_shadow_base::setNoise; // using psd_layer_effects_shadow_base::setFillType; // using psd_layer_effects_shadow_base::setTechnique; // using psd_layer_effects_shadow_base::setRange; // using psd_layer_effects_shadow_base::setJitter; // using psd_layer_effects_shadow_base::setGradient; }; -// oglw: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_25738 +// oglw: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_25738 class KRITAPSD_EXPORT psd_layer_effects_outer_glow : public psd_layer_effects_glow_common { }; -// iglw: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_27692 +// iglw: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_27692 class KRITAPSD_EXPORT psd_layer_effects_inner_glow : public psd_layer_effects_glow_common { public: psd_layer_effects_inner_glow() : m_source(psd_glow_edge) { setInvertsSelection(true); setEdgeHidden(false); setKnocksOut(false); } psd_glow_source source() const { return m_source; } void setSource(psd_glow_source value) { m_source = value; } private: psd_glow_source m_source; }; struct psd_layer_effects_satin : public psd_layer_effects_shadow_base { psd_layer_effects_satin() { setInvert(false); setUseGlobalLight(false); setDistance(8); setSize(7); setSpread(0); setKnocksOut(true); setEdgeHidden(false); setBlendMode(COMPOSITE_LINEAR_BURN); } /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want // using psd_layer_effects_shadow_base::setBlendMode; // using psd_layer_effects_shadow_base::setColor; // using psd_layer_effects_shadow_base::setOpacity; // // NOTE: no global light setting explicitly! // using psd_layer_effects_shadow_base::setAngle; // using psd_layer_effects_shadow_base::setDistance; // using psd_layer_effects_shadow_base::setSize; // using psd_layer_effects_shadow_base::setContourLookupTable; // using psd_layer_effects_shadow_base::setAntiAliased; bool invert() const { return m_invert; } void setInvert(bool value) { m_invert = value; } private: bool m_invert; }; struct psd_pattern_info { qint32 name_length; quint16 * name; quint8 identifier[256]; }; -// bevl: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_31889 +// bevl: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_31889 struct psd_layer_effects_bevel_emboss : public psd_layer_effects_shadow_base { psd_layer_effects_bevel_emboss() : m_style(psd_bevel_inner_bevel), m_technique(psd_technique_softer), m_depth(100), m_direction(psd_direction_up), m_soften(0), m_altitude(30), m_glossAntiAliased(false), m_highlightBlendMode(COMPOSITE_SCREEN), m_highlightColor(Qt::white), m_highlightOpacity(75), m_shadowBlendMode(COMPOSITE_MULT), m_shadowColor(Qt::black), m_shadowOpacity(75), m_contourEnabled(false), m_contourRange(100), m_textureEnabled(false), m_texturePattern(0), m_textureScale(100), m_textureDepth(100), m_textureInvert(false), m_textureAlignWithLayer(true), m_textureHorizontalPhase(0), m_textureVerticalPhase(0) { for(int i = 0; i < PSD_LOOKUP_TABLE_SIZE; ++i) { m_glossContourLookupTable[i] = i; } } /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want // using psd_layer_effects_shadow_base::setSize; // using psd_layer_effects_shadow_base::setAngle; // using psd_layer_effects_shadow_base::setUseGlobalLight; // using psd_layer_effects_shadow_base::setContourLookupTable; // using psd_layer_effects_shadow_base::setAntiAliased; psd_bevel_style style() const { return m_style; } void setStyle(psd_bevel_style value) { m_style = value; } psd_technique_type technique() const { return m_technique; } void setTechnique(psd_technique_type value) { m_technique = value; } int depth() const { return m_depth; } void setDepth(int value) { m_depth = value; } psd_direction direction() const { return m_direction; } void setDirection(psd_direction value) { m_direction = value; } int soften() const { return m_soften; } void setSoften(int value) { m_soften = value; } int altitude() const { return m_altitude; } void setAltitude(int value) { m_altitude = value; } const quint8* glossContourLookupTable() const { return m_glossContourLookupTable; } void setGlossContourLookupTable(const quint8 *value) { memcpy(m_glossContourLookupTable, value, PSD_LOOKUP_TABLE_SIZE * sizeof(quint8)); } bool glossAntiAliased() const { return m_glossAntiAliased; } void setGlossAntiAliased(bool value) { m_glossAntiAliased = value; } QString highlightBlendMode() const { return m_highlightBlendMode; } void setHighlightBlendMode(QString value) { m_highlightBlendMode = value; } QColor highlightColor() const { return m_highlightColor; } void setHighlightColor(QColor value) { m_highlightColor = value; } qint32 highlightOpacity() const { return m_highlightOpacity; } void setHighlightOpacity(qint32 value) { m_highlightOpacity = value; } QString shadowBlendMode() const { return m_shadowBlendMode; } void setShadowBlendMode(QString value) { m_shadowBlendMode = value; } QColor shadowColor() const { return m_shadowColor; } void setShadowColor(QColor value) { m_shadowColor = value; } qint32 shadowOpacity() const { return m_shadowOpacity; } void setShadowOpacity(qint32 value) { m_shadowOpacity = value; } bool contourEnabled() const { return m_contourEnabled; } void setContourEnabled(bool value) { m_contourEnabled = value; } int contourRange() const { return m_contourRange; } void setContourRange(int value) { m_contourRange = value; } bool textureEnabled() const { return m_textureEnabled; } void setTextureEnabled(bool value) { m_textureEnabled = value; } KoPattern* texturePattern() const { return m_texturePattern; } void setTexturePattern(KoPattern *value) { m_texturePattern = value; } int textureScale() const { return m_textureScale; } void setTextureScale(int value) { m_textureScale = value; } int textureDepth() const { return m_textureDepth; } void setTextureDepth(int value) { m_textureDepth = value; } bool textureInvert() const { return m_textureInvert; } void setTextureInvert(bool value) { m_textureInvert = value; } bool textureAlignWithLayer() const { return m_textureAlignWithLayer; } void setTextureAlignWithLayer(bool value) { m_textureAlignWithLayer = value; } void setTexturePhase(const QPointF &phase) { m_textureHorizontalPhase = phase.x(); m_textureVerticalPhase = phase.y(); } QPointF texturePhase() const { return QPointF(m_textureHorizontalPhase, m_textureVerticalPhase); } int textureHorizontalPhase() const { return m_textureHorizontalPhase; } void setTextureHorizontalPhase(int value) { m_textureHorizontalPhase = value; } int textureVerticalPhase() const { return m_textureVerticalPhase; } void setTextureVerticalPhase(int value) { m_textureVerticalPhase = value; } void scaleLinearSizes(qreal scale) override { psd_layer_effects_shadow_base::scaleLinearSizes(scale); m_soften *= scale; m_textureScale *= scale; } private: psd_bevel_style m_style; psd_technique_type m_technique; int m_depth; psd_direction m_direction; // Up or down int m_soften; // Blur value in pixels. int m_altitude; quint8 m_glossContourLookupTable[256]; bool m_glossAntiAliased; QString m_highlightBlendMode; // already in Krita format QColor m_highlightColor; qint32 m_highlightOpacity; // Highlight opacity as a percent QString m_shadowBlendMode; // already in Krita format QColor m_shadowColor; qint32 m_shadowOpacity; // Shadow opacity as a percent bool m_contourEnabled; int m_contourRange; bool m_textureEnabled; KoPattern *m_texturePattern; int m_textureScale; int m_textureDepth; bool m_textureInvert; bool m_textureAlignWithLayer; int m_textureHorizontalPhase; // 0..100% int m_textureVerticalPhase; // 0..100% }; struct psd_layer_effects_overlay_base : public psd_layer_effects_shadow_base { psd_layer_effects_overlay_base() : m_scale(100), m_alignWithLayer(true), m_reverse(false), m_style(psd_gradient_style_linear), m_gradientXOffset(0), m_gradientYOffset(0), m_pattern(0), m_horizontalPhase(0), m_verticalPhase(0) { setUseGlobalLight(false); } /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want // using psd_layer_effects_shadow_base::setBlendMode; // using psd_layer_effects_shadow_base::setOpacity; int scale() const { return m_scale; } bool alignWithLayer() const { return m_alignWithLayer; } bool reverse() const { return m_reverse; } psd_gradient_style style() const { return m_style; } int gradientXOffset() const { return m_gradientXOffset; } int gradientYOffset() const { return m_gradientYOffset; } KoPattern* pattern() const { return m_pattern; } int horizontalPhase() const { return m_horizontalPhase; } int verticalPhase() const { return m_verticalPhase; } // refactor that public: void setScale(int value) { m_scale = value; } void setAlignWithLayer(bool value) { m_alignWithLayer = value; } void setReverse(bool value) { m_reverse = value; } void setStyle(psd_gradient_style value) { m_style = value; } void setGradientOffset(const QPointF &pt) { m_gradientXOffset = qRound(pt.x()); m_gradientYOffset = qRound(pt.y()); } QPointF gradientOffset() const { return QPointF(m_gradientXOffset, m_gradientYOffset); } void setPattern(KoPattern *value) { m_pattern = value; } void setPatternPhase(const QPointF &phase) { m_horizontalPhase = phase.x(); m_verticalPhase = phase.y(); } QPointF patternPhase() const { return QPointF(m_horizontalPhase, m_verticalPhase); } void scaleLinearSizes(qreal scale) override { psd_layer_effects_shadow_base::scaleLinearSizes(scale); m_scale *= scale; } private: // Gradient+Pattern int m_scale; bool m_alignWithLayer; // Gradient bool m_reverse; psd_gradient_style m_style; int m_gradientXOffset; // 0..100% int m_gradientYOffset; // 0..100% // Pattern KoPattern *m_pattern; int m_horizontalPhase; // 0..100% int m_verticalPhase; // 0..100% protected: /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want // must be called in the derived classes' c-tor // using psd_layer_effects_shadow_base::setFillType; }; -// sofi: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_70055 +// sofi: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_70055 struct psd_layer_effects_color_overlay : public psd_layer_effects_overlay_base { psd_layer_effects_color_overlay() { setFillType(psd_fill_solid_color); setColor(Qt::white); } /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want // using psd_layer_effects_shadow_base::setColor; }; struct psd_layer_effects_gradient_overlay : public psd_layer_effects_overlay_base { psd_layer_effects_gradient_overlay() { setFillType(psd_fill_gradient); setAngle(90); setReverse(false); setScale(100); setAlignWithLayer(true); setStyle(psd_gradient_style_linear); } public: /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want // using psd_layer_effects_shadow_base::setGradient; // using psd_layer_effects_shadow_base::setAngle; // using psd_layer_effects_overlay_base::setReverse; // using psd_layer_effects_overlay_base::setScale; // using psd_layer_effects_overlay_base::setAlignWithLayer; // using psd_layer_effects_overlay_base::setStyle; // using psd_layer_effects_overlay_base::setGradientOffset; // using psd_layer_effects_overlay_base::gradientOffset; }; struct psd_layer_effects_pattern_overlay : public psd_layer_effects_overlay_base { psd_layer_effects_pattern_overlay() { setFillType(psd_fill_pattern); setScale(100); setAlignWithLayer(true); } /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want // using psd_layer_effects_overlay_base::setScale; // using psd_layer_effects_overlay_base::setAlignWithLayer; // using psd_layer_effects_overlay_base::setPattern; // using psd_layer_effects_overlay_base::setPatternPhase; // using psd_layer_effects_overlay_base::patternPhase; private: // These are unused /*int m_scale; bool m_alignWithLayer; KoPattern *m_pattern; int m_horizontalPhase; int m_verticalPhase;*/ }; struct psd_layer_effects_stroke : public psd_layer_effects_overlay_base { psd_layer_effects_stroke() : m_position(psd_stroke_outside) { setFillType(psd_fill_solid_color); setColor(Qt::black); setAngle(90); setReverse(false); setScale(100); setAlignWithLayer(true); setStyle(psd_gradient_style_linear); setScale(100); setAlignWithLayer(true); } /// FIXME: 'using' is not supported by MSVC, so please refactor in /// some other way to ensure that the setters are not used /// in the classes we don't want // using psd_layer_effects_shadow_base::setFillType; // using psd_layer_effects_shadow_base::setSize; // using psd_layer_effects_shadow_base::setColor; // using psd_layer_effects_shadow_base::setGradient; // using psd_layer_effects_shadow_base::setAngle; // using psd_layer_effects_overlay_base::setReverse; // using psd_layer_effects_overlay_base::setScale; // using psd_layer_effects_overlay_base::setAlignWithLayer; // using psd_layer_effects_overlay_base::setStyle; // using psd_layer_effects_overlay_base::setGradientOffset; // using psd_layer_effects_overlay_base::gradientOffset; // using psd_layer_effects_overlay_base::setPattern; // using psd_layer_effects_overlay_base::setPatternPhase; // using psd_layer_effects_overlay_base::patternPhase; psd_stroke_position position() const { return m_position; } void setPosition(psd_stroke_position value) { m_position = value; } private: psd_stroke_position m_position; }; /** * Convert PsdColorMode to pigment colormodelid and colordepthid. * @see KoColorModelStandardIds * * @return a QPair containing ColorModelId and ColorDepthID */ QPair KRITAPSD_EXPORT psd_colormode_to_colormodelid(psd_color_mode colormode, quint16 channelDepth); /** * Convert the Photoshop blend mode strings to Pigment compositeop id's */ QString KRITAPSD_EXPORT psd_blendmode_to_composite_op(const QString& blendmode); QString KRITAPSD_EXPORT composite_op_to_psd_blendmode(const QString& compositeOp); #endif // PSD_H diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp index 209a89d592..e2d9bd5412 100644 --- a/libs/ui/KisApplication.cpp +++ b/libs/ui/KisApplication.cpp @@ -1,874 +1,888 @@ /* * Copyright (C) 1998, 1999 Torben Weis * Copyright (C) 2012 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisApplication.h" #include #ifdef Q_OS_WIN #include #include #endif #ifdef Q_OS_MACOS #include "osx.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoConfig.h" #include #include #include #include "thememanager.h" #include "KisPrintJob.h" #include "KisDocument.h" #include "KisMainWindow.h" #include "KisAutoSaveRecoveryDialog.h" #include "KisPart.h" #include #include "kis_md5_generator.h" #include "kis_splash_screen.h" #include "kis_config.h" #include "flake/kis_shape_selection.h" #include #include #include #include #include #include #include #include "kisexiv2/kis_exiv2.h" #include "KisApplicationArguments.h" #include #include "kis_action_registry.h" #include #include #include #include "kis_image_barrier_locker.h" #include "opengl/kis_opengl.h" #include "kis_spin_box_unit_manager.h" #include "kis_document_aware_spin_box_unit_manager.h" #include "KisViewManager.h" #include "kis_workspace_resource.h" #include #include #include #include "widgets/KisScreenColorPicker.h" #include "KisDlgInternalColorSelector.h" #include #include namespace { const QTime appStartTime(QTime::currentTime()); } class KisApplication::Private { public: Private() {} QPointer splashScreen; KisAutoSaveRecoveryDialog *autosaveDialog {0}; QPointer mainWindow; // The first mainwindow we create on startup bool batchRun {false}; + 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"; + QStringList styles = QStringList() << "breeze" << "fusion" << "plastique"; if (!styles.contains(style()->objectName().toLower())) { Q_FOREACH (const QString & style, styles) { if (!setStyle(style)) { qDebug() << "No" << style << "available."; } else { qDebug() << "Set style" << style; break; } } } } else { qDebug() << "Style override disabled, using" << style()->objectName(); } } #if defined(Q_OS_WIN) && defined(ENV32BIT) typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); LPFN_ISWOW64PROCESS fnIsWow64Process; BOOL isWow64() { BOOL bIsWow64 = FALSE; //IsWow64Process is not available on all supported versions of Windows. //Use GetModuleHandle to get a handle to the DLL that contains the function //and GetProcAddress to get a pointer to the function if available. fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress( GetModuleHandle(TEXT("kernel32")),"IsWow64Process"); if(0 != fnIsWow64Process) { if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64)) { //handle error } } return bIsWow64; } #endif void KisApplication::initializeGlobals(const KisApplicationArguments &args) { int dpiX = args.dpiX(); int dpiY = args.dpiY(); if (dpiX > 0 && dpiY > 0) { KoDpi::setDPI(dpiX, dpiY); } } void KisApplication::addResourceTypes() { // qDebug() << "addResourceTypes();"; // All Krita's resource types KoResourcePaths::addResourceType("markers", "data", "/styles/"); KoResourcePaths::addResourceType("kis_pics", "data", "/pics/"); KoResourcePaths::addResourceType("kis_images", "data", "/images/"); KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/"); KoResourcePaths::addResourceType("kis_brushes", "data", "/brushes/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("gmic_definitions", "data", "/gmic/"); KoResourcePaths::addResourceType("kis_resourcebundles", "data", "/bundles/"); KoResourcePaths::addResourceType("kis_defaultpresets", "data", "/defaultpresets/"); KoResourcePaths::addResourceType("kis_paintoppresets", "data", "/paintoppresets/"); KoResourcePaths::addResourceType("kis_workspaces", "data", "/workspaces/"); KoResourcePaths::addResourceType("kis_windowlayouts", "data", "/windowlayouts/"); KoResourcePaths::addResourceType("kis_sessions", "data", "/sessions/"); KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl"); KoResourcePaths::addResourceType("ko_patterns", "data", "/patterns/", true); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/"); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/", true); KoResourcePaths::addResourceType("ko_palettes", "data", "/palettes/", true); KoResourcePaths::addResourceType("kis_shortcuts", "data", "/shortcuts/"); KoResourcePaths::addResourceType("kis_actions", "data", "/actions"); KoResourcePaths::addResourceType("kis_actions", "data", "/pykrita"); KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc"); KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/"); KoResourcePaths::addResourceType("ko_effects", "data", "/effects/"); KoResourcePaths::addResourceType("tags", "data", "/tags/"); KoResourcePaths::addResourceType("templates", "data", "/templates"); KoResourcePaths::addResourceType("pythonscripts", "data", "/pykrita"); KoResourcePaths::addResourceType("symbols", "data", "/symbols"); KoResourcePaths::addResourceType("preset_icons", "data", "/preset_icons"); KoResourcePaths::addResourceType("ko_gamutmasks", "data", "/gamutmasks/", true); // // Extra directories to look for create resources. (Does anyone actually use that anymore?) // KoResourcePaths::addResourceDir("ko_gradients", "/usr/share/create/gradients/gimp"); // KoResourcePaths::addResourceDir("ko_gradients", QDir::homePath() + QString("/.create/gradients/gimp")); // KoResourcePaths::addResourceDir("ko_patterns", "/usr/share/create/patterns/gimp"); // KoResourcePaths::addResourceDir("ko_patterns", QDir::homePath() + QString("/.create/patterns/gimp")); // KoResourcePaths::addResourceDir("kis_brushes", "/usr/share/create/brushes/gimp"); // KoResourcePaths::addResourceDir("kis_brushes", QDir::homePath() + QString("/.create/brushes/gimp")); // KoResourcePaths::addResourceDir("ko_palettes", "/usr/share/create/swatches"); // KoResourcePaths::addResourceDir("ko_palettes", QDir::homePath() + QString("/.create/swatches")); // Make directories for all resources we can save, and tags QDir d; d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tags/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/asl/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/brushes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gradients/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/paintoppresets/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/palettes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/patterns/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/workspaces/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/input/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/pykrita/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/symbols/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/color-schemes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/tool_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/emblem_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gamutmasks/"); } void KisApplication::loadResources() { // qDebug() << "loadResources();"; setSplashScreenLoadingText(i18n("Loading Resources...")); processEvents(); KoResourceServerProvider::instance(); setSplashScreenLoadingText(i18n("Loading Brush Presets...")); processEvents(); KisResourceServerProvider::instance(); setSplashScreenLoadingText(i18n("Loading Brushes...")); processEvents(); KisBrushServer::instance()->brushServer(); setSplashScreenLoadingText(i18n("Loading Bundles...")); processEvents(); KisResourceBundleServerProvider::instance(); } void KisApplication::loadResourceTags() { // qDebug() << "loadResourceTags()"; KoResourceServerProvider::instance()->patternServer()->loadTags(); KoResourceServerProvider::instance()->gradientServer()->loadTags(); KoResourceServerProvider::instance()->paletteServer()->loadTags(); KoResourceServerProvider::instance()->svgSymbolCollectionServer()->loadTags(); KisBrushServer::instance()->brushServer()->loadTags(); KisResourceServerProvider::instance()->workspaceServer()->loadTags(); KisResourceServerProvider::instance()->layerStyleCollectionServer()->loadTags(); KisResourceBundleServerProvider::instance()->resourceBundleServer()->loadTags(); KisResourceServerProvider::instance()->paintOpPresetServer()->loadTags(); KisResourceServerProvider::instance()->paintOpPresetServer()->clearOldSystemTags(); } void KisApplication::loadPlugins() { // qDebug() << "loadPlugins();"; KoShapeRegistry* r = KoShapeRegistry::instance(); r->add(new KisShapeSelectionFactory()); KisActionRegistry::instance(); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); KisPaintOpRegistry::instance(); KoColorSpaceRegistry::instance(); } void KisApplication::loadGuiPlugins() { // qDebug() << "loadGuiPlugins();"; // Load the krita-specific tools setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Tool...")); processEvents(); // qDebug() << "loading tools"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Tool"), QString::fromLatin1("[X-Krita-Version] == 28")); // Load dockers setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Dock...")); processEvents(); // qDebug() << "loading dockers"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Dock"), QString::fromLatin1("[X-Krita-Version] == 28")); // XXX_EXIV: make the exiv io backends real plugins setSplashScreenLoadingText(i18n("Loading Plugins Exiv/IO...")); processEvents(); // qDebug() << "loading exiv2"; KisExiv2::initialize(); } bool KisApplication::start(const KisApplicationArguments &args) { KisConfig cfg(false); #if defined(Q_OS_WIN) #ifdef ENV32BIT if (isWow64() && !cfg.readEntry("WarnedAbout32Bits", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running a 32 bits build on a 64 bits Windows.\n" "This is not recommended.\n" "Please download and install the x64 build instead.")); cfg.writeEntry("WarnedAbout32Bits", true); } #endif #endif QString opengl = cfg.canvasState(); if (opengl == "OPENGL_NOT_TRIED" ) { cfg.setCanvasState("TRY_OPENGL"); } else if (opengl != "OPENGL_SUCCESS" && opengl != "TRY_OPENGL") { cfg.setCanvasState("OPENGL_FAILED"); } setSplashScreenLoadingText(i18n("Initializing Globals")); processEvents(); initializeGlobals(args); const bool doNewImage = args.doNewImage(); const bool doTemplate = args.doTemplate(); const bool exportAs = args.exportAs(); const bool exportSequence = args.exportSequence(); const QString exportFileName = args.exportFileName(); d->batchRun = (exportAs || exportSequence || !exportFileName.isEmpty()); const bool needsMainWindow = (!exportAs && !exportSequence); // only show the mainWindow when no command-line mode option is passed bool showmainWindow = (!exportAs && !exportSequence); // would be !batchRun; const bool showSplashScreen = !d->batchRun && qEnvironmentVariableIsEmpty("NOSPLASH"); if (showSplashScreen && d->splashScreen) { d->splashScreen->show(); d->splashScreen->repaint(); processEvents(); } KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator()); KConfigGroup group(KSharedConfig::openConfig(), "theme"); Digikam::ThemeManager themeManager; themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark")); ResetStarting resetStarting(d->splashScreen, args.filenames().count()); // remove the splash when done Q_UNUSED(resetStarting); // Make sure we can save resources and tags setSplashScreenLoadingText(i18n("Adding resource types")); processEvents(); addResourceTypes(); // Load the plugins loadPlugins(); // Load all resources loadResources(); // Load all the tags loadResourceTags(); // Load the gui plugins loadGuiPlugins(); KisPart *kisPart = KisPart::instance(); if (needsMainWindow) { // show a mainWindow asap, if we want that setSplashScreenLoadingText(i18n("Loading Main Window...")); processEvents(); bool sessionNeeded = true; auto sessionMode = cfg.sessionOnStartup(); if (!args.session().isEmpty()) { sessionNeeded = !kisPart->restoreSession(args.session()); } else if (sessionMode == KisConfig::SOS_ShowSessionManager) { showmainWindow = false; sessionNeeded = false; kisPart->showSessionManager(); } else if (sessionMode == KisConfig::SOS_PreviousSession) { KConfigGroup sessionCfg = KSharedConfig::openConfig()->group("session"); const QString &sessionName = sessionCfg.readEntry("previousSession"); sessionNeeded = !kisPart->restoreSession(sessionName); } if (sessionNeeded) { kisPart->startBlankSession(); } if (!args.windowLayout().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->windowLayoutServer(); KisWindowLayoutResource* windowLayout = rserver->resourceByName(args.windowLayout()); if (windowLayout) { windowLayout->applyLayout(); } } if (showmainWindow) { d->mainWindow = kisPart->currentMainwindow(); if (!args.workspace().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = rserver->resourceByName(args.workspace()); if (workspace) { d->mainWindow->restoreWorkspace(workspace); } } if (args.canvasOnly()) { d->mainWindow->viewManager()->switchCanvasOnly(true); } if (args.fullScreen()) { d->mainWindow->showFullScreen(); } } else { d->mainWindow = kisPart->createMainWindow(); } } short int numberOfOpenDocuments = 0; // number of documents open // Check for autosave files that can be restored, if we're not running a batchrun (test) if (!d->batchRun) { checkAutosaveFiles(); } setSplashScreenLoadingText(QString()); // done loading, so clear out label processEvents(); //configure the unit manager KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(new KisDocumentAwareSpinBoxUnitManagerBuilder()); connect(this, &KisApplication::aboutToQuit, &KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder); //ensure the builder is destroyed when the application leave. //the new syntax slot syntax allow to connect to a non q_object static method. // Create a new image, if needed if (doNewImage) { KisDocument *doc = args.image(); if (doc) { kisPart->addDocument(doc); d->mainWindow->addViewAndNotifyLoadingCompleted(doc); } } // Get the command line arguments which we have to parse int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; argNumber++) { QString fileName = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { // called in mix with batch options? ignore and silently skip if (d->batchRun) { continue; } if (createNewDocFromTemplate(fileName, d->mainWindow)) { ++numberOfOpenDocuments; } // now try to load } else { if (exportAs) { QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName, false); if (outputMimetype == "application/octetstream") { dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl; return false; } KisDocument *doc = kisPart->createDocument(); doc->setFileBatchMode(d->batchRun); bool result = doc->openUrl(QUrl::fromLocalFile(fileName)); if (!result) { errKrita << "Could not load " << fileName << ":" << doc->errorMessage(); QTimer::singleShot(0, this, SLOT(quit())); return false; } if (exportFileName.isEmpty()) { errKrita << "Export destination is not specified for" << fileName << "Please specify export destination with --export-filename option"; QTimer::singleShot(0, this, SLOT(quit())); return false; } qApp->processEvents(); // For vector layers to be updated doc->setFileBatchMode(true); if (!doc->exportDocumentSync(QUrl::fromLocalFile(exportFileName), outputMimetype.toLatin1())) { errKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage(); } QTimer::singleShot(0, this, SLOT(quit())); return true; } else if (exportSequence) { KisDocument *doc = kisPart->createDocument(); doc->setFileBatchMode(d->batchRun); doc->openUrl(QUrl::fromLocalFile(fileName)); qApp->processEvents(); // For vector layers to be updated if (!doc->image()->animationInterface()->hasAnimation()) { errKrita << "This file has no animation." << endl; QTimer::singleShot(0, this, SLOT(quit())); return false; } doc->setFileBatchMode(true); int sequenceStart = 0; KisAsyncAnimationFramesSaveDialog exporter(doc->image(), doc->image()->animationInterface()->fullClipRange(), exportFileName, sequenceStart, 0); exporter.setBatchMode(d->batchRun); KisAsyncAnimationFramesSaveDialog::Result result = exporter.regenerateRange(0); if (result == KisAsyncAnimationFramesSaveDialog::RenderFailed) { errKrita << i18n("Failed to render animation frames!") << endl; } QTimer::singleShot(0, this, SLOT(quit())); return true; } else if (d->mainWindow) { if (fileName.endsWith(".bundle")) { d->mainWindow->installBundle(fileName); } else { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (d->mainWindow->openDocument(QUrl::fromLocalFile(fileName), flags)) { // Normal case, success numberOfOpenDocuments++; } } } } } } // fixes BUG:369308 - Krita crashing on splash screen when loading. // trying to open a file before Krita has loaded can cause it to hang and crash if (d->splashScreen) { d->splashScreen->displayLinks(true); d->splashScreen->displayRecentFiles(true); } + 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() { } void KisApplication::setSplashScreen(QWidget *splashScreen) { d->splashScreen = qobject_cast(splashScreen); } void KisApplication::setSplashScreenLoadingText(QString textToLoad) { if (d->splashScreen) { //d->splashScreen->loadingLabel->setText(textToLoad); d->splashScreen->setLoadingText(textToLoad); d->splashScreen->repaint(); } } void KisApplication::hideSplashScreen() { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } } bool KisApplication::notify(QObject *receiver, QEvent *event) { try { return QApplication::notify(receiver, event); } catch (std::exception &e) { qWarning("Error %s sending event %i to object %s", e.what(), event->type(), qPrintable(receiver->objectName())); } catch (...) { qWarning("Error sending event %i to object %s", event->type(), qPrintable(receiver->objectName())); } return false; } -void KisApplication::remoteArguments(QByteArray message, QObject *socket) +void KisApplication::executeRemoteArguments(QByteArray message, KisMainWindow *mainWindow) { - Q_UNUSED(socket); - - // check if we have any mainwindow - KisMainWindow *mw = qobject_cast(qApp->activeWindow()); - if (!mw) { - mw = KisPart::instance()->mainWindows().first(); - } - - if (!mw) { - return; - } - KisApplicationArguments args = KisApplicationArguments::deserialize(message); const bool doTemplate = args.doTemplate(); const int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; ++argNumber) { QString filename = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { - createNewDocFromTemplate(filename, mw); + createNewDocFromTemplate(filename, mainWindow); } else if (QFile(filename).exists()) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; - mw->openDocument(QUrl::fromLocalFile(filename), flags); + mainWindow->openDocument(QUrl::fromLocalFile(filename), flags); } } } } + +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::clearConfig() { KIS_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread()); KSharedConfigPtr config = KSharedConfig::openConfig(); // find user settings file bool createDir = false; QString kritarcPath = KoResourcePaths::locateLocal("config", "kritarc", createDir); QFile configFile(kritarcPath); if (configFile.exists()) { // clear file if (configFile.open(QFile::WriteOnly)) { configFile.close(); } else { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Failed to clear %1\n\n" "Please make sure no other program is using the file and try again.", kritarcPath), QMessageBox::Ok, QMessageBox::Ok); } } // reload from disk; with the user file settings cleared, // this should load any default configuration files shipping with the program config->reparseConfiguration(); config->sync(); } void KisApplication::askClearConfig() { Qt::KeyboardModifiers mods = QApplication::queryKeyboardModifiers(); bool askClearConfig = (mods & Qt::ControlModifier) && (mods & Qt::ShiftModifier) && (mods & Qt::AltModifier); if (askClearConfig) { bool ok = QMessageBox::question(0, i18nc("@title:window", "Krita"), i18n("Do you want to clear the settings file?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes; if (ok) { clearConfig(); } } } diff --git a/libs/ui/KisApplication.h b/libs/ui/KisApplication.h index 54fed0058c..0b6a7c4dd0 100644 --- a/libs/ui/KisApplication.h +++ b/libs/ui/KisApplication.h @@ -1,124 +1,125 @@ /* * Copyright (C) 1998, 1999 Torben Weis * Copyright (C) 2012 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_APPLICATION_H #define KIS_APPLICATION_H #include #include #include #include "kritaui_export.h" class KisMainWindow; class KisApplicationPrivate; class QWidget; class KisApplicationArguments; class KisAutoSaveRecoveryDialog; #include /** * @brief Base class for the %Krita app * * This class handles arguments given on the command line and * shows a generic about dialog for the Krita app. * * In addition it adds the standard directories where Krita * can find its images etc. * * If the last mainwindow becomes closed, KisApplication automatically * calls QApplication::quit. */ class KRITAUI_EXPORT KisApplication : public QtSingleApplication { Q_OBJECT public: /** * Creates an application object, adds some standard directories and * initializes kimgio. */ explicit KisApplication(const QString &key, int &argc, char **argv); /** * Destructor. */ ~KisApplication() override; /** * Call this to start the application. * * Parses command line arguments and creates the initial main windowss and docs * from them (or an empty doc if no cmd-line argument is specified ). * * You must call this method directly before calling QApplication::exec. * * It is valid behaviour not to call this method at all. In this case you * have to process your command line parameters by yourself. */ virtual bool start(const KisApplicationArguments &args); /** * Checks if user is holding ctrl+alt+shift keys and asks if the settings file should be cleared. * * Typically called during startup before reading the config. */ void askClearConfig(); /** * Tell KisApplication to show this splashscreen when you call start(); * when start returns, the splashscreen is hidden. Use KSplashScreen * to have the splash show correctly on Xinerama displays. */ void setSplashScreen(QWidget *splash); void setSplashScreenLoadingText(QString); void hideSplashScreen(); /// Overridden to handle exceptions from event handlers. bool notify(QObject *receiver, QEvent *event) override; void addResourceTypes(); void loadResources(); void loadResourceTags(); void loadPlugins(); void loadGuiPlugins(); void initializeGlobals(const KisApplicationArguments &args); public Q_SLOTS: + void executeRemoteArguments(QByteArray message, KisMainWindow *mainWindow); void remoteArguments(QByteArray message, QObject*socket); void fileOpenRequested(const QString & url); private: /// @return the number of autosavefiles opened void checkAutosaveFiles(); bool createNewDocFromTemplate(const QString &fileName, KisMainWindow *m_mainWindow); void clearConfig(); private: class Private; QScopedPointer d; class ResetStarting; friend class ResetStarting; }; #endif diff --git a/libs/ui/KisImportExportManager.cpp b/libs/ui/KisImportExportManager.cpp index 69c0fcdd8c..2e5414446c 100644 --- a/libs/ui/KisImportExportManager.cpp +++ b/libs/ui/KisImportExportManager.cpp @@ -1,726 +1,730 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisImportExportManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "KisImportExportFilter.h" #include "KisDocument.h" #include #include #include "kis_painter.h" #include "kis_guides_config.h" #include "kis_grid_config.h" #include "kis_popup_button.h" #include #include "kis_async_action_feedback.h" #include "KisReferenceImagesLayer.h" // static cache for import and export mimetypes QStringList KisImportExportManager::m_importMimeTypes; QStringList KisImportExportManager::m_exportMimeTypes; class Q_DECL_HIDDEN KisImportExportManager::Private { public: KoUpdaterPtr updater; QString cachedExportFilterMimeType; QSharedPointer cachedExportFilter; }; struct KisImportExportManager::ConversionResult { ConversionResult() { } ConversionResult(const QFuture &futureStatus) : m_isAsync(true), m_futureStatus(futureStatus) { } ConversionResult(KisImportExportErrorCode status) : m_isAsync(false), m_status(status) { } bool isAsync() const { return m_isAsync; } QFuture futureStatus() const { // if the result is not async, then it means some failure happened, // just return a cancelled future KIS_SAFE_ASSERT_RECOVER_NOOP(m_isAsync || !m_status.isOk()); return m_futureStatus; } KisImportExportErrorCode status() const { return m_status; } void setStatus(KisImportExportErrorCode value) { m_status = value; } private: bool m_isAsync = false; QFuture m_futureStatus; KisImportExportErrorCode m_status = ImportExportCodes::InternalError; }; KisImportExportManager::KisImportExportManager(KisDocument* document) : m_document(document) , d(new Private) { } KisImportExportManager::~KisImportExportManager() { delete d; } KisImportExportErrorCode KisImportExportManager::importDocument(const QString& location, const QString& mimeType) { ConversionResult result = convert(Import, location, location, mimeType, false, 0, false); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), ImportExportCodes::InternalError); return result.status(); } KisImportExportErrorCode KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, const QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, false); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), ImportExportCodes::InternalError); return result.status(); } QFuture KisImportExportManager::exportDocumentAsyc(const QString &location, const QString &realLocation, const QByteArray &mimeType, KisImportExportErrorCode &status, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, true); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(result.isAsync() || !result.status().isOk(), QFuture()); status = result.status(); return result.futureStatus(); } // The static method to figure out to which parts of the // graph this mimetype has a connection to. QStringList KisImportExportManager::supportedMimeTypes(Direction direction) { // Find the right mimetype by the extension QSet mimeTypes; // mimeTypes << KisDocument::nativeFormatMimeType() << "application/x-krita-paintoppreset" << "image/openraster"; if (direction == KisImportExportManager::Import) { if (m_importMimeTypes.isEmpty()) { QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); Q_FOREACH(const QString &mimetype, json.value("X-KDE-Import").toString().split(",", QString::SkipEmptyParts)) { //qDebug() << "Adding import mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader; mimeTypes << mimetype; } } qDeleteAll(list); m_importMimeTypes = mimeTypes.toList(); } return m_importMimeTypes; } else if (direction == KisImportExportManager::Export) { if (m_exportMimeTypes.isEmpty()) { QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); Q_FOREACH(const QString &mimetype, json.value("X-KDE-Export").toString().split(",", QString::SkipEmptyParts)) { //qDebug() << "Adding export mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader; mimeTypes << mimetype; } } qDeleteAll(list); m_exportMimeTypes = mimeTypes.toList(); } return m_exportMimeTypes; } return QStringList(); } KisImportExportFilter *KisImportExportManager::filterForMimeType(const QString &mimetype, KisImportExportManager::Direction direction) { int weight = -1; KisImportExportFilter *filter = 0; QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); QString directionKey = direction == Export ? "X-KDE-Export" : "X-KDE-Import"; if (json.value(directionKey).toString().split(",", QString::SkipEmptyParts).contains(mimetype)) { KLibFactory *factory = qobject_cast(loader->instance()); if (!factory) { warnUI << loader->errorString(); continue; } QObject* obj = factory->create(0); if (!obj || !obj->inherits("KisImportExportFilter")) { delete obj; continue; } KisImportExportFilter *f = qobject_cast(obj); if (!f) { delete obj; continue; } int w = json.value("X-KDE-Weight").toInt(); if (w > weight) { delete filter; filter = f; f->setObjectName(loader->fileName()); weight = w; } } } qDeleteAll(list); if (filter) { filter->setMimeType(mimetype); } return filter; } bool KisImportExportManager::batchMode(void) const { return m_document->fileBatchMode(); } void KisImportExportManager::setUpdater(KoUpdaterPtr updater) { d->updater = updater; } QString KisImportExportManager::askForAudioFileName(const QString &defaultDir, QWidget *parent) { KoFileDialog dialog(parent, KoFileDialog::ImportFiles, "ImportAudio"); if (!defaultDir.isEmpty()) { dialog.setDefaultDir(defaultDir); } QStringList mimeTypes; mimeTypes << "audio/mpeg"; mimeTypes << "audio/ogg"; mimeTypes << "audio/vorbis"; mimeTypes << "audio/vnd.wave"; mimeTypes << "audio/flac"; dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Open Audio")); return dialog.filename(); } KisImportExportManager::ConversionResult KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync) { // export configuration is supported for export only KIS_SAFE_ASSERT_RECOVER_NOOP(direction == Export || !bool(exportConfiguration)); QString typeName = mimeType; if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(location, direction == KisImportExportManager::Export ? false : true); } QSharedPointer filter; /** * Fetching a filter from the registry is a really expensive operation, * because it blocks all the threads. Cache the filter if possible. */ if (direction == KisImportExportManager::Export && d->cachedExportFilter && d->cachedExportFilterMimeType == typeName) { filter = d->cachedExportFilter; } else { filter = toQShared(filterForMimeType(typeName, direction)); if (direction == Export) { d->cachedExportFilter = filter; d->cachedExportFilterMimeType = typeName; } } if (!filter) { return KisImportExportErrorCode(ImportExportCodes::FileFormatIncorrect); } filter->setFilename(location); filter->setRealFilename(realLocation); filter->setBatchMode(batchMode()); filter->setMimeType(typeName); if (!d->updater.isNull()) { // WARNING: The updater is not guaranteed to be persistent! If you ever want // to add progress reporting to "Save also as .kra", make sure you create // a separate KoProgressUpdater for that! // WARNING2: the failsafe completion of the updater happens in the destructor // the filter. filter->setUpdater(d->updater); } QByteArray from, to; if (direction == Export) { from = m_document->nativeFormatMimeType(); to = typeName.toLatin1(); } else { from = typeName.toLatin1(); to = m_document->nativeFormatMimeType(); } KIS_ASSERT_RECOVER_RETURN_VALUE( direction == Import || direction == Export, KisImportExportErrorCode(ImportExportCodes::InternalError)); // "bad conversion graph" ConversionResult result = KisImportExportErrorCode(ImportExportCodes::OK); if (direction == Import) { KisUsageLogger::log(QString("Importing %1 to %2. Location: %3. Real location: %4. Batchmode: %5") .arg(QString::fromLatin1(from)) .arg(QString::fromLatin1(to)) .arg(location) .arg(realLocation) .arg(batchMode())); // async importing is not yet supported! KIS_SAFE_ASSERT_RECOVER_NOOP(!isAsync); // FIXME: Dmitry says "this progress reporting code never worked. Initial idea was to implement it his way, but I stopped and didn't finish it" if (0 && !batchMode()) { KisAsyncActionFeedback f(i18n("Opening document..."), 0); result = f.runAction(std::bind(&KisImportExportManager::doImport, this, location, filter)); } else { result = doImport(location, filter); } if (result.status().isOk()) { - KisImageSP image = m_document->image(); - KisUsageLogger::log(QString("Loaded image from %1. Size: %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8") - .arg(QString::fromLatin1(from)) - .arg(image->width()) - .arg(image->height()) - .arg(image->xRes()) - .arg(image->colorSpace()->colorModelId().name()) - .arg(image->colorSpace()->colorDepthId().name()) - .arg(image->colorSpace()->profile()->name()) - .arg(image->nlayers())); - + KisImageSP image = m_document->image().toStrongRef(); + if (image) { + KisUsageLogger::log(QString("Loaded image from %1. Size: %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8") + .arg(QString::fromLatin1(from)) + .arg(image->width()) + .arg(image->height()) + .arg(image->xRes()) + .arg(image->colorSpace()->colorModelId().name()) + .arg(image->colorSpace()->colorDepthId().name()) + .arg(image->colorSpace()->profile()->name()) + .arg(image->nlayers())); + } + else { + qWarning() << "The filter returned OK, but there is no image"; + } } else { KisUsageLogger::log(QString("Failed to load image from %1").arg(QString::fromLatin1(from))); } } else /* if (direction == Export) */ { if (!exportConfiguration) { exportConfiguration = filter->lastSavedConfiguration(from, to); } if (exportConfiguration) { fillStaticExportConfigurationProperties(exportConfiguration); } bool alsoAsKra = false; bool askUser = askUserAboutExportConfiguration(filter, exportConfiguration, from, to, batchMode(), showWarnings, &alsoAsKra); if (!batchMode() && !askUser) { return KisImportExportErrorCode(ImportExportCodes::Cancelled); } KisUsageLogger::log(QString("Converting from %1 to %2. Location: %3. Real location: %4. Batchmode: %5. Configuration: %6") .arg(QString::fromLatin1(from)) .arg(QString::fromLatin1(to)) .arg(location) .arg(realLocation) .arg(batchMode()) .arg(exportConfiguration ? exportConfiguration->toXML() : "none")); if (isAsync) { result = QtConcurrent::run(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra)); // we should explicitly report that the exporting has been initiated result.setStatus(ImportExportCodes::OK); } else if (!batchMode()) { KisAsyncActionFeedback f(i18n("Saving document..."), 0); result = f.runAction(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra)); } else { result = doExport(location, filter, exportConfiguration, alsoAsKra); } if (exportConfiguration && !batchMode()) { KisConfig(false).setExportConfiguration(typeName, exportConfiguration); } } return result; } void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration, KisImageSP image) { KisPaintDeviceSP dev = image->projection(); const KoColorSpace* cs = dev->colorSpace(); const bool isThereAlpha = KisPainter::checkDeviceHasTransparency(image->projection()); exportConfiguration->setProperty(KisImportExportFilter::ImageContainsTransparencyTag, isThereAlpha); exportConfiguration->setProperty(KisImportExportFilter::ColorModelIDTag, cs->colorModelId().id()); exportConfiguration->setProperty(KisImportExportFilter::ColorDepthIDTag, cs->colorDepthId().id()); const bool sRGB = (cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive) && !cs->profile()->name().contains(QLatin1String("g10"))); exportConfiguration->setProperty(KisImportExportFilter::sRGBTag, sRGB); } void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration) { return fillStaticExportConfigurationProperties(exportConfiguration, m_document->image()); } bool KisImportExportManager::askUserAboutExportConfiguration( QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, const QByteArray &from, const QByteArray &to, const bool batchMode, const bool showWarnings, bool *alsoAsKra) { // prevents the animation renderer from running this code const QString mimeUserDescription = KisMimeDatabase::descriptionForMimeType(to); QStringList warnings; QStringList errors; { KisPreExportChecker checker; checker.check(m_document->image(), filter->exportChecks()); warnings = checker.warnings(); errors = checker.errors(); } KisConfigWidget *wdg = 0; if (QThread::currentThread() == qApp->thread()) { wdg = filter->createConfigurationWidget(0, from, to); KisMainWindow *kisMain = KisPart::instance()->currentMainwindow(); if (wdg && kisMain) { KisViewManager *manager = kisMain->viewManager(); wdg->setView(manager); } } // Extra checks that cannot be done by the checker, because the checker only has access to the image. if (!m_document->assistants().isEmpty() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains assistants. The assistants will not be saved.")); } if (m_document->referenceImagesLayer() && m_document->referenceImagesLayer()->shapeCount() > 0 && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains reference images. The reference images will not be saved.")); } if (m_document->guidesConfig().hasGuides() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains guides. The guides will not be saved.")); } if (!m_document->gridConfig().isDefault() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains a custom grid configuration. The configuration will not be saved.")); } if (!batchMode && !errors.isEmpty()) { QString error = "

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

" + "

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

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

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

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); } if (wdg) { QGroupBox *box = new QGroupBox(i18n("Options")); QVBoxLayout *boxLayout = new QVBoxLayout(box); wdg->setConfiguration(exportConfiguration); boxLayout->addWidget(wdg); layout->addWidget(box); } QCheckBox *chkAlsoAsKra = 0; if (showWarnings && !warnings.isEmpty()) { chkAlsoAsKra = new QCheckBox(i18n("Also save your image as a Krita file.")); chkAlsoAsKra->setChecked(KisConfig(true).readEntry("AlsoSaveAsKra", false)); layout->addWidget(chkAlsoAsKra); } dlg.setMainWidget(page); dlg.resize(dlg.minimumSize()); if (showWarnings || wdg) { if (!dlg.exec()) { return false; } } *alsoAsKra = false; if (chkAlsoAsKra) { KisConfig(false).writeEntry("AlsoSaveAsKra", chkAlsoAsKra->isChecked()); *alsoAsKra = chkAlsoAsKra->isChecked(); } if (wdg) { *exportConfiguration = *wdg->configuration(); } } return true; } KisImportExportErrorCode KisImportExportManager::doImport(const QString &location, QSharedPointer filter) { QFile file(location); if (!file.exists()) { return ImportExportCodes::FileNotExist; } if (filter->supportsIO() && !file.open(QFile::ReadOnly)) { return KisImportExportErrorCode(KisImportExportErrorCannotRead(file.error())); } KisImportExportErrorCode status = filter->convert(m_document, &file, KisPropertiesConfigurationSP()); if (file.isOpen()) { file.close(); } return status; } KisImportExportErrorCode KisImportExportManager::doExport(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra) { KisImportExportErrorCode status = doExportImpl(location, filter, exportConfiguration); if (alsoAsKra && status.isOk()) { QString kraLocation = location + ".kra"; QByteArray mime = m_document->nativeFormatMimeType(); QSharedPointer filter( filterForMimeType(QString::fromLatin1(mime), Export)); KIS_SAFE_ASSERT_RECOVER_NOOP(filter); if (filter) { filter->setFilename(kraLocation); KisPropertiesConfigurationSP kraExportConfiguration = filter->lastSavedConfiguration(mime, mime); status = doExportImpl(kraLocation, filter, kraExportConfiguration); } else { status = ImportExportCodes::FileFormatIncorrect; } } return status; } // Temporary workaround until QTBUG-57299 is fixed. // 02-10-2019 update: the bug is closed, but we've still seen this issue. // and without using QSaveFile the issue can still occur // when QFile::copy fails because Dropbox/Google/OneDrive // locks the target file. #ifndef Q_OS_WIN #define USE_QSAVEFILE #endif KisImportExportErrorCode KisImportExportManager::doExportImpl(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration) { #ifdef USE_QSAVEFILE QSaveFile file(location); file.setDirectWriteFallback(true); if (filter->supportsIO() && !file.open(QFile::WriteOnly)) { #else QFileInfo fi(location); QTemporaryFile file(QDir::tempPath() + "/.XXXXXX.kra"); if (filter->supportsIO() && !file.open()) { #endif KisImportExportErrorCannotWrite result(file.error()); #ifdef USE_QSAVEFILE file.cancelWriting(); #endif return result; } KisImportExportErrorCode status = filter->convert(m_document, &file, exportConfiguration); if (filter->supportsIO()) { if (!status.isOk()) { #ifdef USE_QSAVEFILE file.cancelWriting(); #endif } else { #ifdef USE_QSAVEFILE if (!file.commit()) { qWarning() << "Could not commit QSaveFile"; status = KisImportExportErrorCannotWrite(file.error()); } #else file.flush(); file.close(); QFile target(location); if (target.exists()) { // There should already be a .kra~ backup target.remove(); } if (!file.copy(location)) { file.setAutoRemove(false); return KisImportExportErrorCannotWrite(file.error()); } #endif } } // Do some minimal verification QString verificationResult = filter->verify(location); if (!verificationResult.isEmpty()) { status = KisImportExportErrorCode(ImportExportCodes::ErrorWhileWriting); m_document->setErrorMessage(verificationResult); } return status; } #include diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index d4f1425b8a..fbd505259b 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2766 +1,2777 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" #include // qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_selection_manager.h" #include "kis_icon_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include "KoToolBoxDocker_p.h" #include #include #include #include #include #include #ifdef Q_OS_ANDROID #include #endif +#include #include #include "dialogs/kis_about_application.h" #include "dialogs/kis_delayed_save_dialog.h" #include "dialogs/kis_dlg_preferences.h" #include "kis_action_manager.h" #include "KisApplication.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_custom_image_widget.h" #include #include "kis_group_layer.h" #include "kis_image_from_clipboard_widget.h" #include "kis_image.h" #include #include "KisImportExportManager.h" #include "kis_mainwindow_observer.h" #include "kis_memory_statistics_server.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "KisResourceServerProvider.h" #include "kis_signal_compressor_with_param.h" #include "kis_statusbar.h" #include "KisView.h" #include "KisViewManager.h" #include "thememanager.h" #include "kis_animation_importer.h" #include "dialogs/kis_dlg_import_image_sequence.h" #include #include "KisWindowLayoutManager.h" #include #include "KisWelcomePageWidget.h" #include #include #include "KisCanvasWindow.h" #include "kis_action.h" #include class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent, QUuid id) : q(parent) , id(id) , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent)) , welcomePage(new KisWelcomePageWidget(parent)) , widgetStack(new QStackedWidget(parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new KisSignalMapper(parent)) , documentMapper(new KisSignalMapper(parent)) #ifdef Q_OS_ANDROID , fileManager(new KisAndroidFileManager(parent)) #endif { if (id.isNull()) this->id = QUuid::createUuid(); welcomeScroller = new QScrollArea(); welcomeScroller->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); welcomeScroller->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); welcomeScroller->setWidget(welcomePage); welcomeScroller->setWidgetResizable(true); widgetStack->addWidget(welcomeScroller); widgetStack->addWidget(mdiArea); mdiArea->setTabsMovable(true); mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder); } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; QUuid id; KisViewManager *viewManager {0}; QPointer activeView; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; // KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; KisAction *toggleDetachCanvas {0}; KisAction *fullScreenMode {0}; KisAction *showSessionManager {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; KoResourceModel *workspacemodel {0}; QScopedPointer undoActionsUpdateManager; QString lastExportLocation; QMap dockWidgetsMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; QScrollArea *welcomeScroller {0}; KisWelcomePageWidget *welcomePage {0}; QStackedWidget *widgetStack {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; KisSignalMapper *windowMapper; KisSignalMapper *documentMapper; KisCanvasWindow *canvasWindow {0}; QByteArray lastExportedFormat; QScopedPointer > tabSwitchCompressor; QMutex savingEntryMutex; KConfigGroup windowStateConfig; QUuid workspaceBorrowedBy; KisSignalAutoConnectionsStore screenConnectionsStore; #ifdef Q_OS_ANDROID KisAndroidFileManager *fileManager; #endif KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow(QUuid uuid) : KXmlGuiWindow() , d(new Private(this, uuid)) { auto rserver = KisResourceServerProvider::instance()->workspaceServer(); QSharedPointer adapter(new KoResourceServerAdapter(rserver)); d->workspacemodel = new KoResourceModel(adapter, this); connect(d->workspacemodel, &KoResourceModel::afterResourcesLayoutReset, this, [&]() { updateWindowMenu(); }); d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow"); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_MACOS setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager, false); // Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created. KoPluginLoader::instance()->load("Krita/ApplicationPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), qApp, true); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); KisConfig cfg(true); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory)); d->toolOptionsDocker->toggleViewAction()->setEnabled(true); } QMap dockwidgetActions; dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction(); Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) { KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker); QDockWidget *dw = createDockWidget(factory); dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction(); } if (d->toolOptionsDocker) { dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction(); } connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*,QList >)), this, SLOT(newOptionWidgets(KoCanvasController*,QList >))); Q_FOREACH (QString title, dockwidgetActions.keys()) { d->dockWidgetMenu->addAction(dockwidgetActions[title]); } Q_FOREACH (QDockWidget *wdg, dockWidgets()) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { wdg->setVisible(true); } } Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) { observer->setObservedCanvas(0); KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer); if (mainwindowObserver) { mainwindowObserver->setViewManager(d->viewManager); } } // Load all the actions from the tool plugins Q_FOREACH(KoToolFactoryBase *toolFactory, KoToolRegistry::instance()->values()) { toolFactory->createActions(actionCollection()); } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); // Tab close button override // Windows just has a black X, and Ubuntu has a dark x that is hard to read // just switch this icon out for all OSs so it is easier to see d->mdiArea->setStyleSheet("QTabBar::close-button { image: url(:/pics/broken-preset.png) }"); setCentralWidget(d->widgetStack); d->widgetStack->setCurrentIndex(0); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); d->canvasWindow = new KisCanvasWindow(this); actionCollection()->addAssociatedWidget(d->canvasWindow); createActions(); // the welcome screen needs to grab actions...so make sure this line goes after the createAction() so they exist d->welcomePage->setMainWindow(this); setAutoSaveSettings(d->windowStateConfig, false); subWindowActivated(); updateWindowMenu(); if (isHelpMenuEnabled() && !d->helpMenu) { // workaround for KHelpMenu (or rather KAboutData::applicationData()) internally // not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard // not having the app version preset // fixed hopefully in KF5 5.22.0, patch pending QGuiApplication *app = qApp; KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion()); aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); d->helpMenu = new KHelpMenu(this, aboutData, false); // workaround-less version: // d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false); // The difference between using KActionCollection->addAction() is that // these actions do not get tied to the MainWindow. What does this all do? KActionCollection *actions = d->viewManager->actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); } // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves QAction *helpAction = actionCollection()->action("help_contents"); helpAction->disconnect(); connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); #if 0 //check for colliding shortcuts QSet existingShortcuts; Q_FOREACH (QAction* action, actionCollection()->actions()) { if(action->shortcut() == QKeySequence(0)) { continue; } dbgKrita << "shortcut " << action->text() << " " << action->shortcut(); Q_ASSERT(!existingShortcuts.contains(action->shortcut())); existingShortcuts.insert(action->shortcut()); } #endif configChanged(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui")); setXMLFile(":/kxmlgui5/krita4.xmlgui"); guiFactory()->addClient(this); connect(guiFactory(), SIGNAL(makingChanges(bool)), SLOT(slotXmlGuiMakingChanges(bool))); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); toolBar->setMovable(KisConfig(true).readEntry("LockAllDockerPanels", false)); if (toolBar) { if (toolBar->objectName() == "BrushesAndStuff") { toolBar->setEnabled(false); } KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); act->setChecked(!toolBar->isHidden()); toolbarList.append(act); } else { warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } } KToolBar::setToolBarsLocked(KisConfig(true).readEntry("LockAllDockerPanels", false)); plugActionList("toolbarlist", toolbarList); d->toolbarList = toolbarList; applyToolBarLayout(); d->viewManager->updateGUI(); d->viewManager->updateIcons(); QTimer::singleShot(1000, this, SLOT(checkSanity())); { using namespace std::placeholders; // For _1 placeholder std::function callback( std::bind(&KisMainWindow::switchTab, this, _1)); d->tabSwitchCompressor.reset( new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE)); } if (cfg.readEntry("CanvasOnlyActive", false)) { QString currentWorkspace = cfg.readEntry("CurrentWorkspace", "Default"); KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = rserver->resourceByName(currentWorkspace); if (workspace) { restoreWorkspace(workspace); } cfg.writeEntry("CanvasOnlyActive", false); menuBar()->setVisible(true); } this->winId(); // Ensures the native window has been created. QWindow *window = this->windowHandle(); connect(window, SIGNAL(screenChanged(QScreen *)), this, SLOT(windowScreenChanged(QScreen *))); } KisMainWindow::~KisMainWindow() { // Q_FOREACH (QAction *ac, actionCollection()->actions()) { // QAction *action = qobject_cast(ac); // if (action) { // qDebug() << "", "").replace("", "") // << "\n\ticonText=" << action->iconText().replace("&", "&") // << "\n\tshortcut=" << action->shortcut().toString() // << "\n\tisCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "\n\tstatusTip=" << action->statusTip() // << "\n/>\n" ; // } // else { // dbgKrita << "Got a non-qaction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); delete d->viewManager; delete d; } QUuid KisMainWindow::id() const { return d->id; } void KisMainWindow::addView(KisView *view, QMdiSubWindow *subWindow) { if (d->activeView == view && !subWindow) return; if (d->activeView) { d->activeView->disconnect(this); } // register the newly created view in the input manager viewManager()->inputManager()->addTrackedCanvas(view->canvasBase()); showView(view, subWindow); updateCaption(); emit restoringDone(); if (d->activeView) { connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified())); connect(d->viewManager->statusBar(), SIGNAL(memoryStatusUpdated()), this, SLOT(updateCaption())); } } void KisMainWindow::notifyChildViewDestroyed(KisView *view) { /** * If we are the last view of the window, Qt will not activate another tab * before destroying tab/window. In 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()); + 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(); - if (d->mdiArea->subWindowList().size() == 1) { - imageView->showMaximized(); - } - else { - imageView->show(); - } - // No, no, no: do not try to call this _before_ the show() has // been called on the view; only when that has happened is the // opengl context active, and very bad things happen if we tell // the dockers to update themselves with a view if the opengl // context is not active. setActiveView(imageView); updateWindowMenu(); updateCaption(); } } void KisMainWindow::slotPreferences() { 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::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the tmp resource } } const QStringList templateDirs = KoResourcePaths::findDirs("templates"); for (QStringList::ConstIterator it = templateDirs.begin() ; ok && it != templateDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the templates directory. break; } } } if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start Q_FOREACH (KisMainWindow *mw, KisPart::instance()->mainWindows()) { if (mw != this) { mw->reloadRecentFileList(); } } } QList KisMainWindow::recentFilesUrls() { return d->recentFiles->urls(); } void KisMainWindow::clearRecentFiles() { d->recentFiles->clear(); d->welcomePage->populateRecentDocuments(); } void KisMainWindow::removeRecentUrl(const QUrl &url) { d->recentFiles->removeUrl(url); KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); } void KisMainWindow::reloadRecentFileList() { d->recentFiles->loadEntries(KSharedConfig::openConfig()->group("RecentFiles")); } void KisMainWindow::updateCaption() { if (!d->mdiArea->activeSubWindow()) { updateCaption(QString(), false); } else if (d->activeView && d->activeView->document() && d->activeView->image()){ KisDocument *doc = d->activeView->document(); QString caption(doc->caption()); if (d->readOnly) { caption += " [" + i18n("Write Protected") + "] "; } if (doc->isRecovered()) { caption += " [" + i18n("Recovered") + "] "; } // show the file size for the document KisMemoryStatisticsServer::Statistics m_fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->activeView ? d->activeView->image() : 0); if (m_fileSizeStats.imageSize) { caption += QString(" (").append( KFormat().formatByteSize(m_fileSizeStats.imageSize)).append( ")"); } updateCaption(caption, doc->isModified()); if (!doc->url().fileName().isEmpty()) { d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName())); } else { d->saveAction->setToolTip(i18n("Save")); } } } void KisMainWindow::updateCaption(const QString &caption, bool modified) { QString versionString = KritaVersionWrapper::versionString(true); QString title = caption; if (!title.contains(QStringLiteral("[*]"))) { // append the placeholder so that the modified mechanism works title.append(QStringLiteral(" [*]")); } if (d->mdiArea->activeSubWindow()) { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) d->mdiArea->activeSubWindow()->setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else d->mdiArea->activeSubWindow()->setWindowTitle(title); #endif d->mdiArea->activeSubWindow()->setWindowModified(modified); } else { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else setWindowTitle(title); #endif } setWindowModified(modified); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags) { if (!QFile(url.toLocalFile()).exists()) { if (!(flags & BatchMode)) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); } d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url, flags); } bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags) { if (!url.isLocalFile()) { qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } KisDocument *newdoc = KisPart::instance()->createDocument(); if (flags & BatchMode) { newdoc->setFileBatchMode(true); } d->firstTime = true; connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); KisDocument::OpenFlags openFlags = KisDocument::None; + // 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); updateReloadFileAction(newdoc); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } + + if (flags & RecoveryFile && + ( url.toLocalFile().startsWith(QDir::tempPath()) + || url.toLocalFile().startsWith(QDir::homePath())) + ) { + newdoc->setUrl(QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) + "/" + QFileInfo(url.toLocalFile()).fileName())); + newdoc->save(false, 0); + } + return true; } void KisMainWindow::showDocument(KisDocument *document) { Q_FOREACH(QMdiSubWindow *subwindow, d->mdiArea->subWindowList()) { KisView *view = qobject_cast(subwindow->widget()); KIS_SAFE_ASSERT_RECOVER_NOOP(view); if (view) { if (view->document() == document) { setActiveSubWindow(subwindow); return; } } } addViewAndNotifyLoadingCompleted(document); } KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document, QMdiSubWindow *subWindow) { showWelcomeScreen(false); // see workaround in function header KisView *view = KisPart::instance()->createView(document, d->viewManager, this); addView(view, subWindow); emit guiLoadingFinished(); return view; } QStringList KisMainWindow::showOpenFileDialog(bool isImporting) { KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images")); return dialog.filenames(); } // Separate from openDocument to handle async loading (remote URLs) void KisMainWindow::slotLoadCompleted() { KisDocument *newdoc = qobject_cast(sender()); if (newdoc && newdoc->image()) { addViewAndNotifyLoadingCompleted(newdoc); disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { - dbgUI << "KisMainWindow::slotLoadCanceled"; + 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) { - dbgUI << "KisMainWindow::slotSaveCanceled"; - if (!errMsg.isEmpty()) // empty when canceled by user + 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() { - dbgUI << "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()); } bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting) { if (!document) { return true; } /** * Make sure that we cannot enter this method twice! * * The lower level functions may call processEvents() so * double-entry is quite possible to achieve. Here we try to lock * the mutex, and if it is failed, just cancel saving. */ StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return false; // no busy wait for saving because it is dangerous! KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); dlg.blockIfImageIsBusy(); if (dlg.result() == KisDelayedSaveDialog::Rejected) { return false; } else if (dlg.result() == KisDelayedSaveDialog::Ignored) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("You are saving a file while the image is " "still rendering. The saved file may be " "incomplete or corrupted.\n\n" "Please select a location where the original " "file will not be overridden!")); saveas = true; } if (document->isRecovered()) { saveas = true; } if (document->url().isEmpty()) { saveas = true; } connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); QByteArray nativeFormat = document->nativeFormatMimeType(); QByteArray oldMimeFormat = document->mimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); if (!mimeFilter.contains(oldMimeFormat)) { dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).completeBaseName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first(); suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (document->url().isEmpty() || isExporting || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs"); dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As")); //qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType()); if (isExporting && !d->lastExportLocation.isEmpty() && !d->lastExportLocation.contains(QDir::tempPath())) { // Use the location where we last exported to, if it's set, as the opening location for the file dialog QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath(); // If the document doesn't have a filename yet, use the title QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).completeBaseName(); // Use the last mimetype we exported to by default QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat; QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,"); // Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true); dialog.setMimeTypeFilters(mimeFilter, proposedMimeType); } else { // Get the last used location for saving KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString proposedPath = group.readEntry("SaveAs", ""); // if that is empty, get the last used location for loading if (proposedPath.isEmpty()) { proposedPath = group.readEntry("OpenDocument", ""); } // If that is empty, too, use the Pictures location. if (proposedPath.isEmpty()) { proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); } // But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise // open the location where the document currently is. dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true); // If exporting, default to all supported file types if user is exporting QByteArray default_mime_type = ""; if (!isExporting) { // otherwise use the document's mimetype, or if that is empty, kra, which is the savest. default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType(); } dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type)); } QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first()); newURL = QUrl::fromLocalFile(fn); } } if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { QString fn = newURL.toLocalFile(); QFileInfo info(fn); document->documentInfo()->setAboutInfo("title", info.completeBaseName()); } QByteArray outputFormat = nativeFormat; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile(), false); outputFormat = outputFormatString.toLatin1(); if (!isExporting) { justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType()); } else { QString path = QFileInfo(d->lastExportLocation).absolutePath(); QString filename = QFileInfo(document->url().toLocalFile()).completeBaseName(); justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path) && (QFileInfo(newURL.toLocalFile()).completeBaseName() == filename) && (outputFormat == d->lastExportedFormat); } bool bOk = true; if (newURL.isEmpty()) { bOk = false; } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions) { if (!document->isNativeFormat(outputFormat)) wantToSave = true; } if (wantToSave) { if (!isExporting) { // Save As ret = document->saveAs(newURL, outputFormat, true); if (ret) { dbgUI << "Successful Save As!"; KisPart::instance()->addRecentURLToAllMainWindows(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; } } else { // Export ret = document->exportDocument(newURL, outputFormat); if (ret) { d->lastExportLocation = newURL.toLocalFile(); d->lastExportedFormat = outputFormat; } } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving // We cannot "export" into the currently // opened document. We are not Gimp. KIS_ASSERT_RECOVER_NOOP(!isExporting); // be sure document has the correct outputMimeType! if (document->isModified()) { ret = document->save(true, 0); } if (!ret) { dbgUI << "Failed Save!"; } } updateReloadFileAction(document); updateCaption(); return ret; } void KisMainWindow::undo() { if (activeView()) { activeView()->document()->undoStack()->undo(); } } void KisMainWindow::redo() { if (activeView()) { activeView()->document()->undoStack()->redo(); } } void KisMainWindow::closeEvent(QCloseEvent *e) { if (hackIsSaving()) { e->setAccepted(false); return; } if (!KisPart::instance()->closingSession()) { QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); if ((action) && (action->isChecked())) { action->setChecked(false); } // Save session when last window is closed if (KisPart::instance()->mainwindowCount() == 1) { bool closeAllowed = KisPart::instance()->closeSession(); if (!closeAllowed) { e->setAccepted(false); return; } } } d->mdiArea->closeAllSubWindows(); QList childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; saveWindowState(true); d->canvasWindow->close(); } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = d->windowStateConfig; KWindowConfig::saveWindowSize(windowHandle(), group); config->sync(); d->windowSizeDirty = false; } if (!d->activeView || d->activeView->document()) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = d->windowStateConfig; saveMainWindowSettings(group); // Save collapsible state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); dockGroup.writeEntry("width", (int) i.value()->widget()->width()); dockGroup.writeEntry("height", (int) i.value()->widget()->height()); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KisMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } void KisMainWindow::setActiveView(KisView* view) { d->activeView = view; updateCaption(); if (d->undoActionsUpdateManager) { d->undoActionsUpdateManager->setCurrentDocument(view ? view->document() : 0); } d->viewManager->setCurrentView(view); KisWindowLayoutManager::instance()->activeDocumentChanged(view->document()); } void KisMainWindow::dragMove(QDragMoveEvent * event) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) { qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!"; } if (tabBar && tabBar->isVisible()) { QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos())); if (tabBar->rect().contains(pos)) { const int tabIndex = tabBar->tabAt(pos); if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) { d->tabSwitchCompressor->start(tabIndex); } } else if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } } void KisMainWindow::dragLeave() { if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } void KisMainWindow::switchTab(int index) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar) return; tabBar->setCurrentIndex(index); } void KisMainWindow::showWelcomeScreen(bool show) { d->widgetStack->setCurrentIndex(!show); } void KisMainWindow::slotFileNew() { const QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import); KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); startupWidget->setWindowModality(Qt::WindowModal); startupWidget->setWindowTitle(i18n("Create new document")); KisConfig cfg(true); int w = cfg.defImageWidth(); int h = cfg.defImageHeight(); const double resolution = cfg.defImageResolution(); const QString colorModel = cfg.defColorModel(); const QString colorDepth = cfg.defaultColorDepth(); const QString colorProfile = cfg.defColorProfile(); CustomDocumentWidgetItem item; item.widget = new KisCustomImageWidget(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.icon = "document-new"; item.title = i18n("Custom Document"); startupWidget->addCustomDocumentWidget(item.widget, item.title, "Custom Document", item.icon); QSize sz = KisClipboard::instance()->clipSize(); if (sz.isValid() && sz.width() != 0 && sz.height() != 0) { w = sz.width(); h = sz.height(); } item.widget = new KisImageFromClipboard(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.title = i18n("Create from Clipboard"); item.icon = "tab-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, "Create from ClipBoard", item.icon); // calls deleteLater connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); // calls deleteLater connect(startupWidget, SIGNAL(openTemplate(QUrl)), KisPart::instance(), SLOT(openTemplate(QUrl))); startupWidget->exec(); // Cancel calls deleteLater... } void KisMainWindow::slotImportFile() { dbgUI << "slotImportFile()"; slotFileOpen(true); } void KisMainWindow::slotFileOpen(bool isImporting) { #ifndef Q_OS_ANDROID QStringList urls = showOpenFileDialog(isImporting); if (urls.isEmpty()) return; Q_FOREACH (const QString& url, urls) { if (!url.isEmpty()) { OpenFlags flags = isImporting ? Import : None; bool res = openDocument(QUrl::fromLocalFile(url), flags); if (!res) { warnKrita << "Loading" << url << "failed"; } } } #else Q_UNUSED(isImporting) d->fileManager->openImportFile(); connect(d->fileManager, SIGNAL(sigFileSelected(QString)), this, SLOT(slotFileSelected(QString))); connect(d->fileManager, SIGNAL(sigEmptyFilePath()), this, SLOT(slotEmptyFilePath())); #endif } void KisMainWindow::slotFileOpenRecent(const QUrl &url) { (void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None); } void KisMainWindow::slotFileSave() { if (saveDocument(d->activeView->document(), false, false)) { emit documentSaved(); } } void KisMainWindow::slotFileSaveAs() { if (saveDocument(d->activeView->document(), true, false)) { emit documentSaved(); } } void KisMainWindow::slotExportFile() { if (saveDocument(d->activeView->document(), true, true)) { emit documentSaved(); } } void KisMainWindow::slotShowSessionManager() { KisPart::instance()->showSessionManager(); } KoCanvasResourceProvider *KisMainWindow::resourceManager() const { return d->viewManager->canvasResourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } const KConfigGroup &KisMainWindow::windowStateConfig() const { return d->windowStateConfig; } void KisMainWindow::saveWindowState(bool restoreNormalState) { if (restoreNormalState) { QAction *showCanvasOnly = d->viewManager->actionCollection()->action("view_show_canvas_only"); if (showCanvasOnly && showCanvasOnly->isChecked()) { showCanvasOnly->setChecked(false); } d->windowStateConfig.writeEntry("ko_geometry", saveGeometry().toBase64()); d->windowStateConfig.writeEntry("State", saveState().toBase64()); if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); } else { saveMainWindowSettings(d->windowStateConfig); } } bool KisMainWindow::restoreWorkspaceState(const QByteArray &state) { QByteArray oldState = saveState(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->toggleViewAction()->setEnabled(true); dock->hide(); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); return false; } return success; } bool KisMainWindow::restoreWorkspace(KisWorkspaceResource *workspace) { bool success = restoreWorkspaceState(workspace->dockerState()); if (activeKisView()) { activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace); } return success; } QByteArray KisMainWindow::borrowWorkspace(KisMainWindow *other) { QByteArray currentWorkspace = saveState(); if (!d->workspaceBorrowedBy.isNull()) { if (other->id() == d->workspaceBorrowedBy) { // We're swapping our original workspace back d->workspaceBorrowedBy = QUuid(); return currentWorkspace; } else { // Get our original workspace back before swapping with a third window KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy); if (borrower) { QByteArray originalLayout = borrower->borrowWorkspace(this); borrower->restoreWorkspaceState(currentWorkspace); d->workspaceBorrowedBy = other->id(); return originalLayout; } } } d->workspaceBorrowedBy = other->id(); return currentWorkspace; } void KisMainWindow::swapWorkspaces(KisMainWindow *a, KisMainWindow *b) { QByteArray workspaceA = a->borrowWorkspace(b); QByteArray workspaceB = b->borrowWorkspace(a); a->restoreWorkspaceState(workspaceB); b->restoreWorkspaceState(workspaceA); } KisViewManager *KisMainWindow::viewManager() const { return d->viewManager; } void KisMainWindow::slotDocumentInfo() { if (!d->activeView->document()) return; KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { d->activeView->document()->setModified(false); } else { d->activeView->document()->setModified(true); } d->activeView->document()->setTitleModified(); } delete dlg; } bool KisMainWindow::slotFileCloseAll() { Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (subwin) { if(!subwin->close()) return false; } } updateCaption(); return true; } void KisMainWindow::slotFileQuit() { // Do not close while KisMainWindow has the savingEntryMutex locked, bug409395. // After the background saving job is initiated, KisDocument blocks closing // while it saves itself. if (hackIsSaving()) { return; } KisPart::instance()->closeSession(); } void KisMainWindow::slotFilePrint() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) { printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point); printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()), activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())), QPrinter::Inch); printJob->startPrinting(KisPrintJob::DeleteWhenDone); } else { delete printJob; } delete printDialog; } void KisMainWindow::slotFilePrintPreview() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KisPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting())); preview->exec(); delete preview; } KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName) { if (!activeView()) return 0; if (!activeView()->document()) return 0; KoPageLayout pageLayout; pageLayout.width = 0; pageLayout.height = 0; pageLayout.topMargin = 0; pageLayout.bottomMargin = 0; pageLayout.leftMargin = 0; pageLayout.rightMargin = 0; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KisDocument* pDoc = d->activeView->document(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.toLocalFile(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); KoUpdaterPtr updater = !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; KisAnimationImporter importer(document->image(), updater); KisImportExportErrorCode status = importer.import(files, firstFrame, step); if (!status.isOk() && !status.isInternalError()) { QString msg = status.errorMessage(); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { saveWindowState(); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(d->windowStateConfig); KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); applyToolBarLayout(); } void KisMainWindow::slotToolbarToggled(bool toggle) { //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) { bar->show(); } else { bar->hide(); } if (d->activeView && d->activeView->document()) { saveWindowState(); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg(false); cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::setMaxRecentItems(uint _number) { d->recentFiles->setMaxItems(_number); } void KisMainWindow::slotReloadFile() { KisDocument* document = d->activeView->document(); if (!document || document->url().isEmpty()) return; if (document->isModified()) { bool ok = QMessageBox::question(this, i18nc("@title:window", "Krita"), i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes; if (!ok) return; } QUrl url = document->url(); saveWindowSettings(); if (!document->reload()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document")); } return; } QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; bool lockAllDockers = KisConfig(true).readEntry("LockAllDockerPanels", false); if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) { warnKrita << "Could not create docker for" << factory->id(); return 0; } dockWidget->setFont(KoDockRegistry::dockFont()); dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); if (lockAllDockers) { if (dockWidget->titleBarWidget()) { dockWidget->titleBarWidget()->setVisible(false); } dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures); } if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } KConfigGroup group = d->windowStateConfig.group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; addDockWidget(side, dockWidget); if (!visible) { dockWidget->hide(); } d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[factory->id()]; } #ifdef Q_OS_MACOS dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); return dockWidget; } void KisMainWindow::forceDockTabFonts() { Q_FOREACH (QObject *child, children()) { if (child->inherits("QTabBar")) { ((QTabBar *)child)->setFont(KoDockRegistry::dockFont()); } } } QList KisMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } QDockWidget* KisMainWindow::dockWidget(const QString &id) { if (!d->dockWidgetsMap.contains(id)) return 0; return d->dockWidgetsMap[id]; } QList KisMainWindow::canvasObservers() const { QList observers; Q_FOREACH (QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } else { warnKrita << docker << "is not a canvas observer"; } } return observers; } void KisMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->dockerStateBeforeHiding = saveState(); Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->dockerStateBeforeHiding); } } void KisMainWindow::slotDocumentTitleModified() { updateCaption(); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } /** * Qt has a weirdness, it has hardcoded shortcuts added to an action * in the window menu. We need to reset the shortcuts for that menu * to nothing, otherwise the shortcuts cannot be made configurable. * * See: https://bugs.kde.org/show_bug.cgi?id=352205 * https://bugs.kde.org/show_bug.cgi?id=375524 * https://bugs.kde.org/show_bug.cgi?id=398729 */ QMdiSubWindow *subWindow = d->mdiArea->currentSubWindow(); if (subWindow) { QMenu *menu = subWindow->systemMenu(); if (menu && menu->actions().size() == 8) { Q_FOREACH (QAction *action, menu->actions()) { action->setShortcut(QKeySequence()); } menu->actions().last()->deleteLater(); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::windowFocused() { /** * Notify selection manager so that it could update selection mask overlay */ if (viewManager() && viewManager()->selectionManager()) { viewManager()->selectionManager()->selectionChanged(); } KisPart *kisPart = KisPart::instance(); KisWindowLayoutManager *layoutManager = KisWindowLayoutManager::instance(); if (!layoutManager->primaryWorkspaceFollowsFocus()) return; QUuid primary = layoutManager->primaryWindowId(); if (primary.isNull()) return; if (d->id == primary) { if (!d->workspaceBorrowedBy.isNull()) { KisMainWindow *borrower = kisPart->windowById(d->workspaceBorrowedBy); if (!borrower) return; swapWorkspaces(this, borrower); } } else { if (d->workspaceBorrowedBy == primary) return; KisMainWindow *primaryWindow = kisPart->windowById(primary); if (!primaryWindow) return; swapWorkspaces(this, primaryWindow); } } void KisMainWindow::updateWindowMenu() { QMenu *menu = d->windowMenu->menu(); menu->clear(); menu->addAction(d->newWindow); menu->addAction(d->documentMenu); QMenu *docMenu = d->documentMenu->menu(); docMenu->clear(); QFontMetrics fontMetrics = docMenu->fontMetrics(); int fileStringWidth = int(QApplication::desktop()->screenGeometry(this).width() * .40f); Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { QString title = fontMetrics.elidedText(doc->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth); if (title.isEmpty() && doc->image()) { title = doc->image()->objectName(); } QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } menu->addAction(d->workspaceMenu); QMenu *workspaceMenu = d->workspaceMenu->menu(); workspaceMenu->clear(); auto workspaces = KisResourceServerProvider::instance()->workspaceServer()->resources(); auto m_this = this; for (auto &w : workspaces) { auto action = workspaceMenu->addAction(w->name()); connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(w); }); } workspaceMenu->addSeparator(); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")), &QAction::triggered, this, [&]() { QString extensions = d->workspacemodel->extensions(); QStringList mimeTypes; for(const QString &suffix : extensions.split(":")) { mimeTypes << KisMimeDatabase::mimeTypeForSuffix(suffix); } KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); QString filename = dialog.filename(); d->workspacemodel->importResourceFile(filename); }); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")), &QAction::triggered, [=]() { QString name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), i18nc("@label:textbox", "Name:")); if (name.isEmpty()) return; auto rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = new KisWorkspaceResource(""); workspace->setDockerState(m_this->saveState()); d->viewManager->canvasResourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString saveLocation = rserver->saveLocation(); bool newName = false; if(name.isEmpty()) { newName = true; name = i18n("Workspace"); } QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + workspace->defaultFileExtension()); i++; } workspace->setFilename(fileInfo.filePath()); if(newName) { name = i18n("Workspace %1", i); } workspace->setName(name); rserver->addResource(workspace); }); // TODO: What to do about delete? // workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace...")); menu->addSeparator(); menu->addAction(d->close); menu->addAction(d->closeAll); if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) { menu->addSeparator(); menu->addAction(d->mdiTile); menu->addAction(d->mdiCascade); } menu->addSeparator(); menu->addAction(d->mdiNextWindow); menu->addAction(d->mdiPreviousWindow); menu->addSeparator(); QList windows = d->mdiArea->subWindowList(); for (int i = 0; i < windows.size(); ++i) { QPointerchild = qobject_cast(windows.at(i)->widget()); if (child && child->document()) { QString text; if (i < 9) { text = i18n("&%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } else { text = i18n("%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } QAction *action = menu->addAction(text); action->setIcon(qApp->windowIcon()); action->setCheckable(true); action->setChecked(child == activeKisView()); connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); d->windowMapper->setMapping(action, windows.at(i)); } } bool showMdiArea = windows.count( ) > 0; if (!showMdiArea) { showWelcomeScreen(true); // see workaround in function in header // keep the recent file list updated when going back to welcome screen reloadRecentFileList(); d->welcomePage->populateRecentDocuments(); } // enable/disable the toolbox docker if there are no documents open Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if ( dw->objectName() == "ToolBox") { dw->setEnabled(showMdiArea); } } } updateCaption(); } void KisMainWindow::setActiveSubWindow(QWidget *window) { if (!window) return; QMdiSubWindow *subwin = qobject_cast(window); //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow; if (subwin && subwin != d->activeSubWindow) { KisView *view = qobject_cast(subwin->widget()); //dbgKrita << "\t" << view << activeView(); if (view && view != activeView()) { d->mdiArea->setActiveSubWindow(subwin); setActiveView(view); } d->activeSubWindow = subwin; } updateWindowMenu(); d->actionManager()->updateGUI(); } void KisMainWindow::configChanged() { KisConfig cfg(true); QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView); d->mdiArea->setViewMode(viewMode); Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); /** * Dirty workaround for a bug in Qt (checked on Qt 5.6.1): * * If you make a window "Show on top" and then switch to the tabbed mode * the window will continue to be painted in its initial "mid-screen" * position. It will persist here until you explicitly switch to its tab. */ if (viewMode == QMdiArea::TabbedView) { Qt::WindowFlags oldFlags = subwin->windowFlags(); Qt::WindowFlags flags = oldFlags; flags &= ~Qt::WindowStaysOnTopHint; flags &= ~Qt::WindowStaysOnBottomHint; if (flags != oldFlags) { subwin->setWindowFlags(flags); subwin->showMaximized(); } } } KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark")); d->actionManager()->updateGUI(); QString s = cfg.getMDIBackgroundColor(); KoColor c = KoColor::fromXML(s); QBrush brush(c.toQColor()); d->mdiArea->setBackground(brush); QString backgroundImage = cfg.getMDIBackgroundImage(); if (backgroundImage != "") { QImage image(backgroundImage); QBrush brush(image); d->mdiArea->setBackground(brush); } d->mdiArea->update(); } KisView* KisMainWindow::newView(QObject *document, QMdiSubWindow *subWindow) { KisDocument *doc = qobject_cast(document); KisView *view = addViewAndNotifyLoadingCompleted(doc, subWindow); d->actionManager()->updateGUI(); return view; } void KisMainWindow::newWindow() { KisMainWindow *mainWindow = KisPart::instance()->createMainWindow(); mainWindow->initializeGeometry(); mainWindow->show(); } void KisMainWindow::closeCurrentWindow() { if (d->mdiArea->currentSubWindow()) { d->mdiArea->currentSubWindow()->close(); d->actionManager()->updateGUI(); } } void KisMainWindow::checkSanity() { // print error if the lcms engine is not available if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { // need to wait 1 event since exiting here would not work. m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); if (rserver->resources().isEmpty()) { m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } } void KisMainWindow::showErrorAndDie() { QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage); if (m_dieOnError) { exit(10); } } void KisMainWindow::showAboutApplication() { KisAboutApplication dlg(this); dlg.exec(); } QPointer KisMainWindow::activeKisView() { if (!d->mdiArea) return 0; QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow(); //dbgKrita << "activeKisView" << activeSubWindow; if (!activeSubWindow) return 0; return qobject_cast(activeSubWindow->widget()); } void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList) { KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController()); bool isOurOwnView = false; Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->canvasController() == controller) { isOurOwnView = view->mainWindow() == this; } } if (!isOurOwnView) return; Q_FOREACH (QWidget *w, optionWidgetList) { #ifdef Q_OS_MACOS w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { QFileInfo info(d->activeView->document()->url().fileName()); title = info.completeBaseName(); } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } void KisMainWindow::createActions() { KisActionManager *actionManager = d->actionManager(); actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew())); actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen())); actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars())); d->fullScreenMode = actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles())); KSharedConfigPtr configPtr = KSharedConfig::openConfig(); d->recentFiles->loadEntries(configPtr->group("RecentFiles")); d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave())); d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE); d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint())); // d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview())); // d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo())); d->undo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo())); d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undoActionsUpdateManager.reset(new KisUndoActionsUpdateManager(d->undo, d->redo)); d->undoActionsUpdateManager->setCurrentDocument(d->activeView ? d->activeView->document() : 0); // d->exportPdf = actionManager->createAction("file_export_pdf"); // connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf())); d->importAnimation = actionManager->createAction("file_import_animation"); connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation())); d->closeAll = actionManager->createAction("file_close_all"); connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll())); // d->reloadFile = actionManager->createAction("file_reload_file"); // d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED); // connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile())); d->importFile = actionManager->createAction("file_import_file"); connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); d->exportFile = actionManager->createAction("file_export_file"); connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile())); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = actionManager->createAction("file_documentinfo"); connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo())); d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this)); d->themeManager->registerThemeActions(actionCollection()); connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); connect(d->themeManager, SIGNAL(signalThemeChanged()), d->welcomePage, SLOT(slotUpdateThemeColors())); d->toggleDockers = actionManager->createAction("view_toggledockers"); KisConfig(true).showDockers(true); d->toggleDockers->setChecked(true); connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool))); d->toggleDetachCanvas = actionManager->createAction("view_detached_canvas"); d->toggleDetachCanvas->setChecked(false); connect(d->toggleDetachCanvas, SIGNAL(toggled(bool)), SLOT(setCanvasDetached(bool))); setCanvasDetached(false); actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); actionCollection()->addAction("window", d->windowMenu); d->mdiCascade = actionManager->createAction("windows_cascade"); connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows())); d->mdiTile = actionManager->createAction("windows_tile"); connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows())); d->mdiNextWindow = actionManager->createAction("windows_next"); connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow())); d->mdiPreviousWindow = actionManager->createAction("windows_previous"); connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow())); d->newWindow = actionManager->createAction("view_newwindow"); connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow())); d->close = actionManager->createStandardAction(KStandardAction::Close, this, SLOT(closeCurrentWindow())); d->showSessionManager = actionManager->createAction("file_sessions"); connect(d->showSessionManager, SIGNAL(triggered(bool)), this, SLOT(slotShowSessionManager())); actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences())); for (int i = 0; i < 2; i++) { d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer")); d->expandingSpacers[i]->setDefaultWidget(new QWidget(this)); d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]); } } void KisMainWindow::applyToolBarLayout() { const bool isPlastiqueStyle = style()->objectName() == "plastique"; Q_FOREACH (KToolBar *toolBar, toolBars()) { toolBar->layout()->setSpacing(4); if (isPlastiqueStyle) { toolBar->setContentsMargins(0, 0, 0, 2); } //Hide text for buttons with an icon in the toolbar Q_FOREACH (QAction *ac, toolBar->actions()){ if (ac->icon().pixmap(QSize(1,1)).isNull() == false){ ac->setPriority(QAction::LowPriority); }else { ac->setIcon(QIcon()); } } } } void KisMainWindow::initializeGeometry() { // if the user didn's specify the geometry on the command line (does anyone do that still?), // we first figure out some good default size and restore the x,y position. See bug 285804Z. KConfigGroup cfg = d->windowStateConfig; QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); if (!restoreGeometry(geom)) { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QGuiApplication::screens().at(scnum)->availableVirtualGeometry(); quint32 x = desk.x(); quint32 y = desk.y(); quint32 w = 0; quint32 h = 0; // Default size -- maximize on small screens, something useful on big screens const int deskWidth = desk.width(); if (deskWidth > 1024) { // a nice width, and slightly less than total available // height to compensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::showManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisMainWindow::windowScreenChanged(QScreen *screen) { emit screenChanged(); d->screenConnectionsStore.clear(); d->screenConnectionsStore.addConnection(screen, SIGNAL(physicalDotsPerInchChanged(qreal)), this, SIGNAL(screenChanged())); } void KisMainWindow::slotXmlGuiMakingChanges(bool finished) { if (finished) { subWindowActivated(); } } #include diff --git a/libs/ui/KisReferenceImage.cpp b/libs/ui/KisReferenceImage.cpp index c39acebe0d..b1c0a774bc 100644 --- a/libs/ui/KisReferenceImage.cpp +++ b/libs/ui/KisReferenceImage.cpp @@ -1,360 +1,370 @@ /* * Copyright (C) 2017 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisReferenceImage.h" #include #include #include #include #include #include +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) +#include +#endif #include #include #include #include #include #include #include #include #include #include #include struct KisReferenceImage::Private : public QSharedData { // Filename within .kra (for embedding) QString internalFilename; // File on disk (for linking) QString externalFilename; QImage image; QImage cachedImage; KisQImagePyramid mipmap; qreal saturation{1.0}; int id{-1}; bool embed{true}; bool loadFromFile() { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!externalFilename.isEmpty(), false); - return image.load(externalFilename); + bool r = image.load(externalFilename); + // See https://bugs.kde.org/show_bug.cgi?id=416515 -- a jpeg image + // loaded into a qimage cannot be saved to png unless we explicitly + // convert the colorspace of the QImage +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + image.convertToColorSpace(QColorSpace(QColorSpace::SRgb)); +#endif + return r; } bool loadFromClipboard() { image = KisClipboardUtil::getImageFromClipboard(); return !image.isNull(); } void updateCache() { if (saturation < 1.0) { cachedImage = KritaUtils::convertQImageToGrayA(image); if (saturation > 0.0) { QPainter gc2(&cachedImage); gc2.setOpacity(saturation); gc2.drawImage(QPoint(), image); } } else { cachedImage = image; } mipmap = KisQImagePyramid(cachedImage); } }; KisReferenceImage::SetSaturationCommand::SetSaturationCommand(const QList &shapes, qreal newSaturation, KUndo2Command *parent) : KUndo2Command(kundo2_i18n("Set saturation"), parent) , newSaturation(newSaturation) { images.reserve(shapes.count()); Q_FOREACH(auto *shape, shapes) { auto *reference = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_BREAK(reference); images.append(reference); } Q_FOREACH(auto *image, images) { oldSaturations.append(image->saturation()); } } void KisReferenceImage::SetSaturationCommand::undo() { auto saturationIterator = oldSaturations.begin(); Q_FOREACH(auto *image, images) { image->setSaturation(*saturationIterator); image->update(); saturationIterator++; } } void KisReferenceImage::SetSaturationCommand::redo() { Q_FOREACH(auto *image, images) { image->setSaturation(newSaturation); image->update(); } } KisReferenceImage::KisReferenceImage() : d(new Private()) { setKeepAspectRatio(true); } KisReferenceImage::KisReferenceImage(const KisReferenceImage &rhs) : KoTosContainer(rhs) , d(rhs.d) {} KisReferenceImage::~KisReferenceImage() {} KisReferenceImage * KisReferenceImage::fromFile(const QString &filename, const KisCoordinatesConverter &converter, QWidget *parent) { KisReferenceImage *reference = new KisReferenceImage(); reference->d->externalFilename = filename; bool ok = reference->d->loadFromFile(); if (ok) { QRect r = QRect(QPoint(), reference->d->image.size()); QSizeF shapeSize = converter.imageToDocument(r).size(); reference->setSize(shapeSize); } else { delete reference; if (parent) { QMessageBox::critical(parent, i18nc("@title:window", "Krita"), i18n("Could not load %1.", filename)); } return nullptr; } return reference; } KisReferenceImage *KisReferenceImage::fromClipboard(const KisCoordinatesConverter &converter) { KisReferenceImage *reference = new KisReferenceImage(); bool ok = reference->d->loadFromClipboard(); if (ok) { QRect r = QRect(QPoint(), reference->d->image.size()); QSizeF size = converter.imageToDocument(r).size(); reference->setSize(size); } else { delete reference; reference = nullptr; } return reference; } void KisReferenceImage::paint(QPainter &gc, KoShapePaintingContext &/*paintcontext*/) const { if (!parent()) return; gc.save(); QSizeF shapeSize = size(); QTransform transform = QTransform::fromScale(shapeSize.width() / d->image.width(), shapeSize.height() / d->image.height()); if (d->cachedImage.isNull()) { // detach the data const_cast(this)->d->updateCache(); } qreal scale; QImage prescaled = d->mipmap.getClosest(transform * gc.transform(), &scale); transform.scale(1.0 / scale, 1.0 / scale); gc.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); gc.setClipRect(QRectF(QPointF(), shapeSize), Qt::IntersectClip); gc.setTransform(transform, true); gc.drawImage(QPoint(), prescaled); gc.restore(); } void KisReferenceImage::setSaturation(qreal saturation) { d->saturation = saturation; d->cachedImage = QImage(); } qreal KisReferenceImage::saturation() const { return d->saturation; } void KisReferenceImage::setEmbed(bool embed) { KIS_SAFE_ASSERT_RECOVER_RETURN(embed || !d->externalFilename.isEmpty()); d->embed = embed; } bool KisReferenceImage::embed() { return d->embed; } bool KisReferenceImage::hasLocalFile() { return !d->externalFilename.isEmpty(); } QString KisReferenceImage::filename() const { return d->externalFilename; } QString KisReferenceImage::internalFile() const { return d->internalFilename; } void KisReferenceImage::setFilename(const QString &filename) { d->externalFilename = filename; d->embed = false; } QColor KisReferenceImage::getPixel(QPointF position) { if (transparency() == 1.0) return Qt::transparent; const QSizeF shapeSize = size(); const QTransform scale = QTransform::fromScale(d->image.width() / shapeSize.width(), d->image.height() / shapeSize.height()); const QTransform transform = absoluteTransformation().inverted() * scale; const QPointF localPosition = position * transform; if (d->cachedImage.isNull()) { d->updateCache(); } return d->cachedImage.pixelColor(localPosition.toPoint()); } void KisReferenceImage::saveXml(QDomDocument &document, QDomElement &parentElement, int id) { d->id = id; QDomElement element = document.createElement("referenceimage"); if (d->embed) { d->internalFilename = QString("reference_images/%1.png").arg(id); } const QString src = d->embed ? d->internalFilename : (QString("file://") + d->externalFilename); element.setAttribute("src", src); const QSizeF &shapeSize = size(); element.setAttribute("width", KisDomUtils::toString(shapeSize.width())); element.setAttribute("height", KisDomUtils::toString(shapeSize.height())); element.setAttribute("keepAspectRatio", keepAspectRatio() ? "true" : "false"); element.setAttribute("transform", SvgUtil::transformToString(transform())); element.setAttribute("opacity", KisDomUtils::toString(1.0 - transparency())); element.setAttribute("saturation", KisDomUtils::toString(d->saturation)); parentElement.appendChild(element); } KisReferenceImage * KisReferenceImage::fromXml(const QDomElement &elem) { auto *reference = new KisReferenceImage(); const QString &src = elem.attribute("src"); if (src.startsWith("file://")) { reference->d->externalFilename = src.mid(7); reference->d->embed = false; } else { reference->d->internalFilename = src; reference->d->embed = true; } qreal width = KisDomUtils::toDouble(elem.attribute("width", "100")); qreal height = KisDomUtils::toDouble(elem.attribute("height", "100")); reference->setSize(QSizeF(width, height)); reference->setKeepAspectRatio(elem.attribute("keepAspectRatio", "true").toLower() == "true"); auto transform = SvgTransformParser(elem.attribute("transform")).transform(); reference->setTransformation(transform); qreal opacity = KisDomUtils::toDouble(elem.attribute("opacity", "1")); reference->setTransparency(1.0 - opacity); qreal saturation = KisDomUtils::toDouble(elem.attribute("saturation", "1")); reference->setSaturation(saturation); return reference; } bool KisReferenceImage::saveImage(KoStore *store) const { if (!d->embed) return true; if (!store->open(d->internalFilename)) { return false; } bool saved = false; KoStoreDevice storeDev(store); if (storeDev.open(QIODevice::WriteOnly)) { saved = d->image.save(&storeDev, "PNG"); } return store->close() && saved; } bool KisReferenceImage::loadImage(KoStore *store) { if (!d->embed) { return d->loadFromFile(); } if (!store->open(d->internalFilename)) { return false; } KoStoreDevice storeDev(store); if (!storeDev.open(QIODevice::ReadOnly)) { return false; } if (!d->image.load(&storeDev, "PNG")) { return false; } return store->close(); } KoShape *KisReferenceImage::cloneShape() const { return new KisReferenceImage(*this); } diff --git a/libs/ui/KisViewManager.cpp b/libs/ui/KisViewManager.cpp index 7926d17ef4..e5560ff946 100644 --- a/libs/ui/KisViewManager.cpp +++ b/libs/ui/KisViewManager.cpp @@ -1,1447 +1,1447 @@ /* * 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 + * 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 "dialogs/kis_dlg_blacklist_cleanup.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 "KisPrintJob.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", KoResourceItemChooserSync::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(); QString defaultPresetName = "basic_tip_default"; bool foundTip = false; for (int i=0; iresourceCount(); i++) { KisPaintOpPresetSP resource = rserver->resources().at(i); if (resource->name().toLower().contains("basic_tip_default")) { defaultPresetName = resource->name(); foundTip = true; } else if (foundTip == false && (resource->name().toLower().contains("default") || resource->filename().toLower().contains("default"))) { defaultPresetName = resource->name(); foundTip = true; } } KisConfig cfg(true); QString lastPreset = cfg.readEntry("LastPreset", defaultPresetName); KisPaintOpPresetSP preset = rserver->resourceByName(lastPreset); if (!preset) { preset = rserver->resourceByName(defaultPresetName); } if (!preset && !rserver->resources().isEmpty()) { preset = rserver->resources().first(); } if (preset) { paintOpBox()->restoreResource(preset.data()); 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()); } a = actionManager()->createAction("edit_blacklist_cleanup"); connect(a, SIGNAL(triggered()), this, SLOT(slotBlacklistCleanup())); 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(); } void KisViewManager::slotBlacklistCleanup() { KisDlgBlacklistCleanup dialog; dialog.exec(); } 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 fileName = document()->localFilePath(); // 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(); 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; } document()->setFileBatchMode(true); document()->saveAs(QUrl::fromUserInput(fileName), document()->mimeType(), true); document()->setFileBatchMode(false); 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 fileName = document()->localFilePath(); // 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(fileName, backupFileName); document()->saveAs(QUrl::fromUserInput(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 = document()->localFilePath(); 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(fileName, backupFileName); document()->saveAs(QUrl::fromUserInput(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()->updateGUI(); } 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(); } diff --git a/libs/ui/KisWindowLayoutResource.cpp b/libs/ui/KisWindowLayoutResource.cpp index 2cc94b57d4..bcb6d4b4fc 100644 --- a/libs/ui/KisWindowLayoutResource.cpp +++ b/libs/ui/KisWindowLayoutResource.cpp @@ -1,450 +1,450 @@ /* * Copyright (c) 2018 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisWindowLayoutResource.h" #include "KisWindowLayoutManager.h" #include #include #include #include #include #include #include #include #include #include #include static const int WINDOW_LAYOUT_VERSION = 1; struct KisWindowLayoutResource::Private { struct WindowGeometry{ int screen = -1; Qt::WindowStates stateFlags = Qt::WindowNoState; QByteArray data; static WindowGeometry fromWindow(const QWidget *window, QList screens) { WindowGeometry geometry; QWindow *windowHandle = window->windowHandle(); geometry.data = window->saveGeometry(); geometry.stateFlags = windowHandle->windowState(); int index = screens.indexOf(windowHandle->screen()); if (index >= 0) { geometry.screen = index; } return geometry; } void forceOntoCorrectScreen(QWidget *window, QList screens) { QWindow *windowHandle = window->windowHandle(); if (screens.indexOf(windowHandle->screen()) != screen) { QScreen *qScreen = screens[screen]; windowHandle->setScreen(qScreen); windowHandle->setPosition(qScreen->availableGeometry().topLeft()); } if (stateFlags) { window->setWindowState(stateFlags); } } void save(QDomDocument &doc, QDomElement &elem) const { if (screen >= 0) { elem.setAttribute("screen", screen); } if (stateFlags & Qt::WindowMaximized) { elem.setAttribute("maximized", "1"); } QDomElement geometry = doc.createElement("geometry"); geometry.appendChild(doc.createCDATASection(data.toBase64())); elem.appendChild(geometry); } static WindowGeometry load(const QDomElement &element) { WindowGeometry geometry; geometry.screen = element.attribute("screen", "-1").toInt(); if (element.attribute("maximized", "0") != "0") { geometry.stateFlags |= Qt::WindowMaximized; } QDomElement dataElement = element.firstChildElement("geometry"); geometry.data = QByteArray::fromBase64(dataElement.text().toLatin1()); return geometry; } }; struct Window { QUuid windowId; QByteArray windowState; WindowGeometry geometry; bool canvasDetached = false; WindowGeometry canvasWindowGeometry; }; QVector windows; - bool showImageInAllWindows; - bool primaryWorkspaceFollowsFocus; + bool showImageInAllWindows {false}; + bool primaryWorkspaceFollowsFocus {false}; QUuid primaryWindow; Private() = default; explicit Private(QVector windows) : windows(std::move(windows)) {} void openNecessaryWindows(QList> ¤tWindows) { auto *kisPart = KisPart::instance(); Q_FOREACH(const Window &window, windows) { QPointer mainWindow = kisPart->windowById(window.windowId); if (mainWindow.isNull()) { mainWindow = kisPart->createMainWindow(window.windowId); currentWindows.append(mainWindow); mainWindow->show(); } } } void closeUnneededWindows(QList> ¤tWindows) { QVector> windowsToClose; Q_FOREACH(KisMainWindow *mainWindow, currentWindows) { bool keep = false; Q_FOREACH(const Window &window, windows) { if (window.windowId == mainWindow->id()) { keep = true; break; } } if (!keep) { windowsToClose.append(mainWindow); // Set the window hidden to prevent "show image in all windows" feature from opening new views on it // while we migrate views onto the remaining windows mainWindow->hide(); } } migrateViewsFromClosingWindows(windowsToClose); Q_FOREACH(QPointer mainWindow, windowsToClose) { mainWindow->close(); } } void migrateViewsFromClosingWindows(QVector> &closingWindows) const { auto *kisPart = KisPart::instance(); KisMainWindow *migrationTarget = nullptr; Q_FOREACH(KisMainWindow *mainWindow, kisPart->mainWindows()) { if (!closingWindows.contains(mainWindow)) { migrationTarget = mainWindow; break; } } if (!migrationTarget) { qWarning() << "Problem: window layout with no windows would leave user with zero main windows."; migrationTarget = closingWindows.takeLast(); migrationTarget->show(); } QVector visibleDocuments; Q_FOREACH(KisView *view, kisPart->views()) { KisMainWindow *window = view->mainWindow(); if (!closingWindows.contains(window)) { visibleDocuments.append(view->document()); } } Q_FOREACH(KisDocument *document, kisPart->documents()) { if (!visibleDocuments.contains(document)) { visibleDocuments.append(document); migrationTarget->newView(document); } } } QList getScreensInConsistentOrder() { QList screens = QGuiApplication::screens(); std::sort(screens.begin(), screens.end(), [](const QScreen *a, const QScreen *b) { QRect aRect = a->geometry(); QRect bRect = b->geometry(); if (aRect.y() == bRect.y()) return aRect.x() < bRect.x(); return (aRect.y() < aRect.y()); }); return screens; } }; KisWindowLayoutResource::KisWindowLayoutResource(const QString &filename) : KoResource(filename) , d(new Private) {} KisWindowLayoutResource::~KisWindowLayoutResource() {} KisWindowLayoutResource * KisWindowLayoutResource::fromCurrentWindows( const QString &filename, const QList> &mainWindows, bool showImageInAllWindows, bool primaryWorkspaceFollowsFocus, KisMainWindow *primaryWindow ) { auto resource = new KisWindowLayoutResource(filename); resource->setWindows(mainWindows); resource->d->showImageInAllWindows = showImageInAllWindows; resource->d->primaryWorkspaceFollowsFocus = primaryWorkspaceFollowsFocus; resource->d->primaryWindow = primaryWindow->id(); return resource; } void KisWindowLayoutResource::applyLayout() { auto *kisPart = KisPart::instance(); auto *layoutManager= KisWindowLayoutManager::instance(); layoutManager->setLastUsedLayout(this); QList> currentWindows = kisPart->mainWindows(); if (d->windows.isEmpty()) { // No windows defined (e.g. fresh new session). Leave things as they are, but make sure there's at least one visible main window if (kisPart->mainwindowCount() == 0) { kisPart->createMainWindow(); } else { kisPart->mainWindows().first()->show(); } } else { d->openNecessaryWindows(currentWindows); d->closeUnneededWindows(currentWindows); } // Wait for the windows to finish opening / closing before applying saved geometry. // If we don't, the geometry may get reset after we apply it. QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); Q_FOREACH(const auto &window, d->windows) { QPointer mainWindow = kisPart->windowById(window.windowId); KIS_SAFE_ASSERT_RECOVER_BREAK(mainWindow); mainWindow->restoreGeometry(window.geometry.data); mainWindow->restoreWorkspaceState(window.windowState); mainWindow->setCanvasDetached(window.canvasDetached); if (window.canvasDetached) { QWidget *canvasWindow = mainWindow->canvasWindow(); canvasWindow->restoreGeometry(window.canvasWindowGeometry.data); } } QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); QList screens = d->getScreensInConsistentOrder(); Q_FOREACH(const auto &window, d->windows) { Private::WindowGeometry geometry = window.geometry; QPointer mainWindow = kisPart->windowById(window.windowId); KIS_SAFE_ASSERT_RECOVER_BREAK(mainWindow); if (geometry.screen >= 0 && geometry.screen < screens.size()) { geometry.forceOntoCorrectScreen(mainWindow, screens); } if (window.canvasDetached) { Private::WindowGeometry canvasWindowGeometry = window.canvasWindowGeometry; if (canvasWindowGeometry.screen >= 0 && canvasWindowGeometry.screen < screens.size()) { canvasWindowGeometry.forceOntoCorrectScreen(mainWindow->canvasWindow(), screens); } } } layoutManager->setShowImageInAllWindowsEnabled(d->showImageInAllWindows); layoutManager->setPrimaryWorkspaceFollowsFocus(d->primaryWorkspaceFollowsFocus, d->primaryWindow); } bool KisWindowLayoutResource::save() { if (filename().isEmpty()) return false; QFile file(filename()); file.open(QIODevice::WriteOnly); bool res = saveToDevice(&file); file.close(); return res; } bool KisWindowLayoutResource::load() { if (filename().isEmpty()) return false; QFile file(filename()); if (file.size() == 0) return false; if (!file.open(QIODevice::ReadOnly)) { warnKrita << "Can't open file " << filename(); return false; } bool res = loadFromDevice(&file); file.close(); return res; } bool KisWindowLayoutResource::saveToDevice(QIODevice *dev) const { QDomDocument doc; QDomElement root = doc.createElement("WindowLayout"); root.setAttribute("name", name()); root.setAttribute("version", WINDOW_LAYOUT_VERSION); saveXml(doc, root); doc.appendChild(root); QTextStream textStream(dev); textStream.setCodec("UTF-8"); doc.save(textStream, 4); KoResource::saveToDevice(dev); return true; } bool KisWindowLayoutResource::loadFromDevice(QIODevice *dev) { QDomDocument doc; if (!doc.setContent(dev)) { return false; } QDomElement element = doc.documentElement(); setName(element.attribute("name")); d->windows.clear(); loadXml(element); setValid(true); return true; } void KisWindowLayoutResource::saveXml(QDomDocument &doc, QDomElement &root) const { root.setAttribute("showImageInAllWindows", (int)d->showImageInAllWindows); root.setAttribute("primaryWorkspaceFollowsFocus", (int)d->primaryWorkspaceFollowsFocus); root.setAttribute("primaryWindow", d->primaryWindow.toString()); Q_FOREACH(const auto &window, d->windows) { QDomElement elem = doc.createElement("window"); elem.setAttribute("id", window.windowId.toString()); window.geometry.save(doc, elem); if (window.canvasDetached) { QDomElement canvasWindowElement = doc.createElement("canvasWindow"); window.canvasWindowGeometry.save(doc, canvasWindowElement); elem.appendChild(canvasWindowElement); } QDomElement state = doc.createElement("windowState"); state.appendChild(doc.createCDATASection(window.windowState.toBase64())); elem.appendChild(state); root.appendChild(elem); } } void KisWindowLayoutResource::loadXml(const QDomElement &element) const { d->showImageInAllWindows = KisDomUtils::toInt(element.attribute("showImageInAllWindows", "0")); d->primaryWorkspaceFollowsFocus = KisDomUtils::toInt(element.attribute("primaryWorkspaceFollowsFocus", "0")); d->primaryWindow = element.attribute("primaryWindow"); for (auto windowElement = element.firstChildElement("window"); !windowElement.isNull(); windowElement = windowElement.nextSiblingElement("window")) { Private::Window window; window.windowId = QUuid(windowElement.attribute("id", QUuid().toString())); if (window.windowId.isNull()) { window.windowId = QUuid::createUuid(); } window.geometry = Private::WindowGeometry::load(windowElement); QDomElement canvasWindowElement = windowElement.firstChildElement("canvasWindow"); if (!canvasWindowElement.isNull()) { window.canvasDetached = true; window.canvasWindowGeometry = Private::WindowGeometry::load(canvasWindowElement); } QDomElement state = windowElement.firstChildElement("windowState"); window.windowState = QByteArray::fromBase64(state.text().toLatin1()); d->windows.append(window); } } QString KisWindowLayoutResource::defaultFileExtension() const { return QString(".kwl"); } void KisWindowLayoutResource::setWindows(const QList> &mainWindows) { d->windows.clear(); QList screens = d->getScreensInConsistentOrder(); Q_FOREACH(auto window, mainWindows) { if (!window->isVisible()) continue; Private::Window state; state.windowId = window->id(); state.windowState = window->saveState(); state.geometry = Private::WindowGeometry::fromWindow(window, screens); state.canvasDetached = window->canvasDetached(); if (state.canvasDetached) { state.canvasWindowGeometry = Private::WindowGeometry::fromWindow(window->canvasWindow(), screens); } d->windows.append(state); } } diff --git a/libs/ui/canvas/kis_canvas_widget_base.cpp b/libs/ui/canvas/kis_canvas_widget_base.cpp index dd1bbdd48f..4a86defdc3 100644 --- a/libs/ui/canvas/kis_canvas_widget_base.cpp +++ b/libs/ui/canvas/kis_canvas_widget_base.cpp @@ -1,276 +1,276 @@ /* * Copyright (C) 2007, 2010 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_canvas_widget_base.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_coordinates_converter.h" #include "kis_canvas_decoration.h" #include "kis_config.h" #include "kis_canvas2.h" #include "KisViewManager.h" #include "kis_selection_manager.h" #include "KisDocument.h" #include "kis_update_info.h" #include "KisQPainterStateSaver.h" struct KisCanvasWidgetBase::Private { public: Private(KisCanvas2 *newCanvas, KisCoordinatesConverter *newCoordinatesConverter) : canvas(newCanvas) , coordinatesConverter(newCoordinatesConverter) , viewConverter(newCanvas->viewConverter()) , toolProxy(newCanvas->toolProxy()) , ignorenextMouseEventExceptRightMiddleClick(0) , borderColor(Qt::gray) {} QList decorations; KisCanvas2 * canvas; KisCoordinatesConverter *coordinatesConverter; const KoViewConverter * viewConverter; KoToolProxy * toolProxy; QTimer blockMouseEvent; - bool ignorenextMouseEventExceptRightMiddleClick; // HACK work around Qt bug not sending tablet right/dblclick http://bugreports.qt.nokia.com/browse/QTBUG-8598 + bool ignorenextMouseEventExceptRightMiddleClick; // HACK work around Qt bug not sending tablet right/dblclick https://bugreports.qt.io/browse/QTBUG-8598 QColor borderColor; }; KisCanvasWidgetBase::KisCanvasWidgetBase(KisCanvas2 * canvas, KisCoordinatesConverter *coordinatesConverter) : m_d(new Private(canvas, coordinatesConverter)) { m_d->blockMouseEvent.setSingleShot(true); } KisCanvasWidgetBase::~KisCanvasWidgetBase() { /** * Clear all the attached decoration. Otherwise they might decide * to process some events or signals after the canvas has been * destroyed */ //5qDeleteAll(m_d->decorations); m_d->decorations.clear(); delete m_d; } void KisCanvasWidgetBase::drawDecorations(QPainter & gc, const QRect &updateWidgetRect) const { if (!m_d->canvas) { dbgFile<<"canvas doesn't exist, in canvas widget base!"; return; } gc.save(); // Setup the painter to take care of the offset; all that the // classes that do painting need to keep track of is resolution gc.setRenderHint(QPainter::Antialiasing); gc.setRenderHint(QPainter::TextAntialiasing); // This option does not do anything anymore with Qt4.6, so don't re-enable it since it seems to break display - // http://www.archivum.info/qt-interest@trolltech.com/2010-01/00481/Re:-(Qt-interest)-Is-QPainter::HighQualityAntialiasing-render-hint-broken-in-Qt-4.6.html + // https://lists.qt-project.org/pipermail/qt-interest-old/2009-December/017078.html // gc.setRenderHint(QPainter::HighQualityAntialiasing); gc.setRenderHint(QPainter::SmoothPixmapTransform); { KisQPainterStateSaver paintShapesState(&gc); gc.setClipRect(updateWidgetRect); gc.setTransform(m_d->coordinatesConverter->documentToWidgetTransform()); // Paint the shapes (other than the layers) m_d->canvas->globalShapeManager()->paint(gc, false); // draw green selection outlines around text shapes that are edited, so the user sees where they end gc.setPen( Qt::green ); Q_FOREACH (KoShape *shape, canvas()->shapeManager()->selection()->selectedShapes()) { if (shape->shapeId() == "ArtisticText" || shape->shapeId() == "TextShapeID") { gc.save(); gc.setTransform(shape->absoluteTransformation(), true); gc.drawRect(QRectF(QPointF(), shape->size())); gc.restore(); } } // Draw text shape over canvas while editing it, that's needs to show the text selection correctly QString toolId = KoToolManager::instance()->activeToolId(); if (toolId == "ArtisticTextTool" || toolId == "TextTool") { gc.save(); gc.setTransform(m_d->coordinatesConverter->documentToWidgetTransform()); gc.setPen(Qt::NoPen); gc.setBrush(Qt::NoBrush); Q_FOREACH (KoShape *shape, canvas()->shapeManager()->selection()->selectedShapes()) { if (shape->shapeId() == "ArtisticText" || shape->shapeId() == "TextShapeID") { KoShapePaintingContext paintContext(canvas(), false); KoShapeManager::renderSingleShape(shape, gc, paintContext); } } gc.restore(); } } // ask the decorations to paint themselves // decorations are painted in "widget" coordinate system Q_FOREACH (KisCanvasDecorationSP deco, m_d->decorations) { deco->paint(gc, m_d->coordinatesConverter->widgetToDocument(updateWidgetRect), m_d->coordinatesConverter,m_d->canvas); } { KisQPainterStateSaver paintDecorationsState(&gc); gc.setTransform(m_d->coordinatesConverter->flakeToWidgetTransform()); // - some tools do not restore gc, but that is not important here // - we need to disable clipping to draw handles properly gc.setClipping(false); toolProxy()->paint(gc, *m_d->viewConverter); } gc.restore(); } void KisCanvasWidgetBase::addDecoration(KisCanvasDecorationSP deco) { m_d->decorations.push_back(deco); std::stable_sort(m_d->decorations.begin(), m_d->decorations.end(), KisCanvasDecoration::comparePriority); } void KisCanvasWidgetBase::removeDecoration(const QString &id) { for (auto it = m_d->decorations.begin(); it != m_d->decorations.end(); ++it) { if ((*it)->id() == id) { it = m_d->decorations.erase(it); break; } } } KisCanvasDecorationSP KisCanvasWidgetBase::decoration(const QString& id) const { Q_FOREACH (KisCanvasDecorationSP deco, m_d->decorations) { if (deco->id() == id) { return deco; } } return 0; } void KisCanvasWidgetBase::setDecorations(const QList &decorations) { m_d->decorations=decorations; std::stable_sort(m_d->decorations.begin(), m_d->decorations.end(), KisCanvasDecoration::comparePriority); } QList KisCanvasWidgetBase::decorations() const { return m_d->decorations; } void KisCanvasWidgetBase::setWrapAroundViewingMode(bool value) { Q_UNUSED(value); } QImage KisCanvasWidgetBase::createCheckersImage(qint32 checkSize) { KisConfig cfg(true); if(checkSize < 0) checkSize = cfg.checkSize(); QColor checkColor1 = cfg.checkersColor1(); QColor checkColor2 = cfg.checkersColor2(); QImage tile(checkSize * 2, checkSize * 2, QImage::Format_RGB32); QPainter pt(&tile); pt.fillRect(tile.rect(), checkColor2); pt.fillRect(0, 0, checkSize, checkSize, checkColor1); pt.fillRect(checkSize, checkSize, checkSize, checkSize, checkColor1); pt.end(); return tile; } void KisCanvasWidgetBase::notifyConfigChanged() { KisConfig cfg(true); m_d->borderColor = cfg.canvasBorderColor(); } QColor KisCanvasWidgetBase::borderColor() const { return m_d->borderColor; } KisCanvas2 *KisCanvasWidgetBase::canvas() const { return m_d->canvas; } KisCoordinatesConverter* KisCanvasWidgetBase::coordinatesConverter() const { return m_d->coordinatesConverter; } QVector KisCanvasWidgetBase::updateCanvasProjection(const QVector &infoObjects) { QVector dirtyViewRects; Q_FOREACH (KisUpdateInfoSP info, infoObjects) { dirtyViewRects << this->updateCanvasProjection(info); } return dirtyViewRects; } KoToolProxy *KisCanvasWidgetBase::toolProxy() const { return m_d->toolProxy; } QVariant KisCanvasWidgetBase::processInputMethodQuery(Qt::InputMethodQuery query) const { if (query == Qt::ImMicroFocus) { QRectF rect = m_d->toolProxy->inputMethodQuery(query, *m_d->viewConverter).toRectF(); return m_d->coordinatesConverter->flakeToWidget(rect); } return m_d->toolProxy->inputMethodQuery(query, *m_d->viewConverter); } void KisCanvasWidgetBase::processInputMethodEvent(QInputMethodEvent *event) { m_d->toolProxy->inputMethodEvent(event); } diff --git a/libs/ui/dialogs/kis_about_application.cpp b/libs/ui/dialogs/kis_about_application.cpp index 5b518c1dd8..5b41403b34 100644 --- a/libs/ui/dialogs/kis_about_application.cpp +++ b/libs/ui/dialogs/kis_about_application.cpp @@ -1,214 +1,212 @@ /* * Copyright (c) 2014 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_about_application.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../krita/data/splash/splash_screen.xpm" #include "../../krita/data/splash/splash_holidays.xpm" #include "../../krita/data/splash/splash_screen_x2.xpm" #include "../../krita/data/splash/splash_holidays_x2.xpm" #include "kis_splash_screen.h" KisAboutApplication::KisAboutApplication(QWidget *parent) : QDialog(parent) { setWindowTitle(i18n("About Krita")); QVBoxLayout *vlayout = new QVBoxLayout(this); vlayout->setMargin(0); QTabWidget *wdgTab = new QTabWidget; vlayout->addWidget(wdgTab); KisSplashScreen *splash = 0; QDate currentDate = QDate::currentDate(); if (currentDate > QDate(currentDate.year(), 12, 4) || currentDate < QDate(currentDate.year(), 1, 9)) { splash = new KisSplashScreen(qApp->applicationVersion(), QPixmap(splash_holidays_xpm), QPixmap(splash_holidays_x2_xpm)); } else { splash = new KisSplashScreen(qApp->applicationVersion(), QPixmap(splash_screen_xpm), QPixmap(splash_screen_x2_xpm)); } splash->setWindowFlags(Qt::Widget); splash->displayLinks(true); splash->setFixedSize(splash->sizeHint()); wdgTab->addTab(splash, i18n("About")); setMinimumSize(wdgTab->sizeHint()); QTextEdit *lblAuthors = new QTextEdit(); lblAuthors->setReadOnly(true); QString authors = i18n("" "" "" "

Created By

" "

"); QFile fileDevelopers(":/developers.txt"); Q_ASSERT(fileDevelopers.exists()); fileDevelopers.open(QIODevice::ReadOnly); Q_FOREACH (const QByteArray &author, fileDevelopers.readAll().split('\n')) { authors.append(QString::fromUtf8(author)); authors.append(", "); } authors.chop(2); authors.append(".

"); lblAuthors->setText(authors); wdgTab->addTab(lblAuthors, i18nc("Heading for the list of Krita authors/developers", "Authors")); QTextEdit *lblKickstarter = new QTextEdit(); lblKickstarter->setReadOnly(true); QString backers = i18n("" "" "" "

Backed By

" "

"); QFile fileBackers(":/backers.txt"); Q_ASSERT(fileBackers.exists()); fileBackers.open(QIODevice::ReadOnly | QIODevice::Text); QTextStream backersText(&fileBackers); backersText.setCodec("UTF-8"); backers.append(backersText.readAll().replace("\n", ", ")); backers.chop(2); backers.append(i18n(".

Thanks! You were all awesome!

")); lblKickstarter->setText(backers); wdgTab->addTab(lblKickstarter, i18n("Backers")); QTextEdit *lblCredits = new QTextEdit(); lblCredits->setReadOnly(true); QString credits = i18n("" "" "" "

Thanks To

" "

"); QFile fileCredits(":/credits.txt"); Q_ASSERT(fileCredits.exists()); fileCredits.open(QIODevice::ReadOnly); Q_FOREACH (const QString &credit, QString::fromUtf8(fileCredits.readAll()).split('\n', QString::SkipEmptyParts)) { if (credit.contains(":")) { QList creditSplit = credit.split(':'); credits.append(creditSplit.at(0)); credits.append(" (" + creditSplit.at(1) + ")"); credits.append(", "); } } credits.chop(2); credits.append(i18n(".

For supporting Krita development with advice, icons, brush sets and more.

")); lblCredits->setText(credits); wdgTab->addTab(lblCredits, i18n("Also Thanks To")); QTextEdit *lblLicense = new QTextEdit(); lblLicense->setReadOnly(true); QString license = i18n("" "" "" "

Your Rights

" "

Krita is released under the GNU General Public License (version 3 or any later version).

" "

This license grants people a number of freedoms:

" "
    " "
  • You are free to use Krita, for any purpose
  • " "
  • You are free to distribute Krita
  • " "
  • You can study how Krita works and change it
  • " "
  • You can distribute changed versions of Krita
  • " "
" "

The Krita Foundation and its projects on krita.org are committed to preserving Krita as free software.

" "

Your artwork

" "

What you create with Krita is your sole property. All your artwork is free for you to use as you like.

" "

That means that Krita can be used commercially, for any purpose. There are no restrictions whatsoever.

" "

Krita’s GNU GPL license guarantees you this freedom. Nobody is ever permitted to take it away, in contrast " "to trial or educational versions of commercial software that will forbid your work in commercial situations.

" "

");
 
     QFile licenseFile(":/LICENSE");
     Q_ASSERT(licenseFile.exists());
     licenseFile.open(QIODevice::ReadOnly);
     QByteArray ba = licenseFile.readAll();
     license.append(QString::fromUtf8(ba));
     license.append("
"); lblLicense->setText(license); wdgTab->addTab(lblLicense, i18n("License")); QTextBrowser *lblThirdParty = new QTextBrowser(); lblThirdParty->setOpenExternalLinks(true); QFile thirdPartyFile(":/libraries.txt"); if (thirdPartyFile.open(QIODevice::ReadOnly)) { ba = thirdPartyFile.readAll(); QString thirdPartyHtml = i18n("" "" "" "

Third-party Libraries used by Krita

" "

Krita is built on the following free software libraries:

    "); Q_FOREACH(const QString &lib, QString::fromUtf8(ba).split('\n')) { if (!lib.startsWith("#")) { QStringList parts = lib.split(','); if (parts.size() >= 3) { thirdPartyHtml.append(QString("
  • %1: %3
  • ").arg(parts[0], parts[1], parts[2])); } } } thirdPartyHtml.append("

      "); lblThirdParty->setText(thirdPartyHtml); } wdgTab->addTab(lblThirdParty, i18n("Third-party libraries")); - QPushButton *bnClose = new QPushButton(i18n("Close")); + bnClose->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); connect(bnClose, SIGNAL(clicked()), SLOT(close())); QHBoxLayout *hlayout = new QHBoxLayout; hlayout->setMargin(10); hlayout->addStretch(10); hlayout->addWidget(bnClose); - - vlayout->addLayout(hlayout); } diff --git a/libs/ui/dialogs/kis_dlg_filter.cpp b/libs/ui/dialogs/kis_dlg_filter.cpp index c8294b07f8..f01e7b93ed 100644 --- a/libs/ui/dialogs/kis_dlg_filter.cpp +++ b/libs/ui/dialogs/kis_dlg_filter.cpp @@ -1,250 +1,251 @@ /* * Copyright (c) 2007 Cyrille Berger * Copyright (c) 2008 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_dlg_filter.h" #include #include #include #include #include #include #include #include #include #include #include "kis_selection.h" #include "kis_node_commands_adapter.h" #include "kis_filter_manager.h" #include "ui_wdgfilterdialog.h" #include "kis_canvas2.h" struct KisDlgFilter::Private { Private(KisFilterManager *_filterManager, KisViewManager *_view) : currentFilter(0) , resizeCount(0) , view(_view) , filterManager(_filterManager) , blockModifyingActionsGuard(new KisInputActionGroupsMaskGuard(view->canvasBase(), ViewTransformActionGroup)) { } KisFilterSP currentFilter; Ui_FilterDialog uiFilterDialog; KisNodeSP node; int resizeCount; KisViewManager *view; KisFilterManager *filterManager; // a special guard object that blocks all the painting input actions while the // dialog is open QScopedPointer blockModifyingActionsGuard; }; KisDlgFilter::KisDlgFilter(KisViewManager *view, KisNodeSP node, KisFilterManager *filterManager, QWidget *parent) : QDialog(parent), d(new Private(filterManager, view)) { setModal(false); d->uiFilterDialog.setupUi(this); d->node = node; d->uiFilterDialog.filterSelection->setView(view); d->uiFilterDialog.filterSelection->showFilterGallery(KisConfig(true).showFilterGallery()); d->uiFilterDialog.pushButtonCreateMaskEffect->show(); connect(d->uiFilterDialog.pushButtonCreateMaskEffect, SIGNAL(pressed()), SLOT(createMask())); connect(d->uiFilterDialog.pushButtonCreateMaskEffect,SIGNAL(pressed()),SLOT(close())); d->uiFilterDialog.filterGalleryToggle->setChecked(d->uiFilterDialog.filterSelection->isFilterGalleryVisible()); d->uiFilterDialog.filterGalleryToggle->setIcon(QPixmap(":/pics/sidebaricon.png")); d->uiFilterDialog.filterGalleryToggle->setMaximumWidth(d->uiFilterDialog.filterGalleryToggle->height()); connect(d->uiFilterDialog.filterSelection, SIGNAL(sigFilterGalleryToggled(bool)), d->uiFilterDialog.filterGalleryToggle, SLOT(setChecked(bool))); connect(d->uiFilterDialog.filterGalleryToggle, SIGNAL(toggled(bool)), d->uiFilterDialog.filterSelection, SLOT(showFilterGallery(bool))); connect(d->uiFilterDialog.filterSelection, SIGNAL(sigSizeChanged()), this, SLOT(slotFilterWidgetSizeChanged())); if (node->inherits("KisMask")) { d->uiFilterDialog.pushButtonCreateMaskEffect->setVisible(false); } d->uiFilterDialog.filterSelection->setPaintDevice(true, d->node->original()); connect(d->uiFilterDialog.buttonBox, SIGNAL(accepted()), SLOT(accept())); connect(d->uiFilterDialog.buttonBox, SIGNAL(rejected()), SLOT(reject())); connect(d->uiFilterDialog.checkBoxPreview, SIGNAL(toggled(bool)), SLOT(enablePreviewToggled(bool))); connect(d->uiFilterDialog.filterSelection, SIGNAL(configurationChanged()), SLOT(filterSelectionChanged())); connect(this, SIGNAL(accepted()), SLOT(slotOnAccept())); + connect(this, SIGNAL(accepted()), d->uiFilterDialog.filterSelection, SLOT(slotBookMarkCurrentFilter())); connect(this, SIGNAL(rejected()), SLOT(slotOnReject())); KConfigGroup group( KSharedConfig::openConfig(), "filterdialog"); d->uiFilterDialog.checkBoxPreview->setChecked(group.readEntry("showPreview", true)); restoreGeometry(KisConfig(true).readEntry("filterdialog/geometry", QByteArray())); } KisDlgFilter::~KisDlgFilter() { KisConfig(false).writeEntry("filterdialog/geometry", saveGeometry()); delete d; } void KisDlgFilter::setFilter(KisFilterSP f) { Q_ASSERT(f); setDialogTitle(f); d->uiFilterDialog.filterSelection->setFilter(f); d->uiFilterDialog.pushButtonCreateMaskEffect->setEnabled(f->supportsAdjustmentLayers()); d->currentFilter = f; updatePreview(); } void KisDlgFilter::setDialogTitle(KisFilterSP filter) { setWindowTitle(filter.isNull() ? i18nc("@title:window", "Filter") : i18nc("@title:window", "Filter: %1", filter->name())); } void KisDlgFilter::startApplyingFilter(KisFilterConfigurationSP config) { if (!d->uiFilterDialog.filterSelection->configuration()) return; if (d->node->inherits("KisPaintLayer")) { config->setChannelFlags(qobject_cast(d->node.data())->channelLockFlags()); } d->filterManager->apply(config); } void KisDlgFilter::updatePreview() { KisFilterConfigurationSP config = d->uiFilterDialog.filterSelection->configuration(); if (!config) return; bool maskCreationAllowed = !d->currentFilter || d->currentFilter->configurationAllowedForMask(config); d->uiFilterDialog.pushButtonCreateMaskEffect->setEnabled(maskCreationAllowed); if (d->uiFilterDialog.checkBoxPreview->isChecked()) { KisFilterConfigurationSP config(d->uiFilterDialog.filterSelection->configuration()); startApplyingFilter(config); } d->uiFilterDialog.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); } void KisDlgFilter::adjustSize() { QWidget::adjustSize(); } void KisDlgFilter::slotFilterWidgetSizeChanged() { QMetaObject::invokeMethod(this, "adjustSize", Qt::QueuedConnection); } void KisDlgFilter::slotOnAccept() { if (!d->filterManager->isStrokeRunning()) { KisFilterConfigurationSP config(d->uiFilterDialog.filterSelection->configuration()); startApplyingFilter(config); } d->filterManager->finish(); d->uiFilterDialog.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); KisConfig(false).setShowFilterGallery(d->uiFilterDialog.filterSelection->isFilterGalleryVisible()); } void KisDlgFilter::slotOnReject() { if (d->filterManager->isStrokeRunning()) { d->filterManager->cancel(); } KisConfig(false).setShowFilterGallery(d->uiFilterDialog.filterSelection->isFilterGalleryVisible()); } void KisDlgFilter::createMask() { if (d->node->inherits("KisMask")) return; if (d->filterManager->isStrokeRunning()) { d->filterManager->cancel(); } KisLayer *layer = qobject_cast(d->node.data()); KisFilterMaskSP mask = new KisFilterMask(); mask->setName(d->currentFilter->name()); mask->initSelection(d->view->selection(), layer); mask->setFilter(d->uiFilterDialog.filterSelection->configuration()); Q_ASSERT(layer->allowAsChild(mask)); KisNodeCommandsAdapter adapter(d->view); adapter.addNode(mask, layer, layer->lastChild()); } void KisDlgFilter::enablePreviewToggled(bool state) { if (state) { updatePreview(); } else if (d->filterManager->isStrokeRunning()) { d->filterManager->cancel(); } KConfigGroup group( KSharedConfig::openConfig(), "filterdialog"); group.writeEntry("showPreview", d->uiFilterDialog.checkBoxPreview->isChecked()); group.config()->sync(); } void KisDlgFilter::filterSelectionChanged() { KisFilterSP filter = d->uiFilterDialog.filterSelection->currentFilter(); setDialogTitle(filter); d->currentFilter = filter; d->uiFilterDialog.pushButtonCreateMaskEffect->setEnabled(filter.isNull() ? false : filter->supportsAdjustmentLayers()); updatePreview(); } void KisDlgFilter::resizeEvent(QResizeEvent* event) { QDialog::resizeEvent(event); // // Workaround, after the initialisation don't center the dialog anymore // if(d->resizeCount < 2) { // QWidget* canvas = d->view->canvas(); // QRect rect(canvas->mapToGlobal(canvas->geometry().topLeft()), size()); // int deltaX = (canvas->geometry().width() - geometry().width())/2; // int deltaY = (canvas->geometry().height() - geometry().height())/2; // rect.translate(deltaX, deltaY); // setGeometry(rect); // d->resizeCount++; // } } diff --git a/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp b/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp index 9a830700ab..f1b547f0cc 100644 --- a/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp +++ b/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp @@ -1,395 +1,383 @@ /* * Copyright (c) 2016 Kapustin Alexey * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * 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_stroke_selection_properties.h" #include #include #include #include #include #include #include #include #include #include #include #include "KoColorProfile.h" #include "KoColorSpaceRegistry.h" #include "KoColor.h" #include "KoColorConversionTransformation.h" #include "KoColorPopupAction.h" #include "kis_icon_utils.h" #include "KoID.h" #include "kis_image.h" #include "kis_annotation.h" #include "kis_config.h" #include "kis_signal_compressor.h" #include "widgets/kis_cmb_idlist.h" #include #include "kis_layer_utils.h" #include #include "kis_canvas_resource_provider.h" #include "KoUnit.h" #include "kis_display_color_converter.h" KisDlgStrokeSelection::KisDlgStrokeSelection(KisImageWSP image, KisViewManager *view, bool isVectorLayer) : KoDialog(view->mainWindow()) { m_resourceManager = view->mainWindow()->resourceManager(); + KisPropertiesConfigurationSP cfg = KisConfig(true).exportConfiguration("StrokeSelection"); converter = view->canvasBase()->displayColorConverter(); setButtons(Ok | Cancel); setDefaultButton(Ok); setCaption(i18nc("@title:window", "Stroke Selection Properties")); m_page = new WdgStrokeSelection(this); + m_page->m_isVectorLayer = isVectorLayer; + m_page->m_cfg = cfg; m_image = image; setMainWidget(m_page); - resize(m_page->sizeHint()); - - KisPropertiesConfigurationSP cfg = KisConfig(true).exportConfiguration("StrokeSelection"); - auto &m_options = m_page->m_options; + StrokeSelectionOptions &m_options = m_page->m_options; m_options.color = cfg->getColor("color"); m_options.lineColorSource = cfg->getInt("lineColorSource"); m_page->lineColorBox->setCurrentIndex(m_options.lineColorSource); m_page->colorSelector->setColor(getSelectedColor().toQColor()); m_options.brushSelected = cfg->getBool("useBrush", 0); m_page->typeBox->setCurrentIndex(m_options.brushSelected? 0 : 1); m_options._colorFillSource = cfg->getInt("colorFillSource", 0); m_page->fillBox->setCurrentIndex(m_options._colorFillSource); m_options.customColor = cfg->getColor("customColor"); + if (m_options._colorFillSource == static_cast(colorFillSource::CustomColor)) { m_page->colorFillSelector->setColor(m_options.customColor.toQColor()); } else { m_page->colorFillSelector->setColor(getFillSelectedColor().toQColor()); } - m_options.fillColor = cfg->getColor("fillColor"); - if (m_options._colorFillSource == static_cast(colorFillSource::None)) { - m_page->colorFillSelector->setDisabled(true); - } - else { - m_page->colorFillSelector->setDisabled(false); } - m_options.lineSize = cfg->getInt("lineSize", 1); m_page->lineSize->setValue(m_options.lineSize); - if (m_options.brushSelected) { - m_page->lineSize->setDisabled(true); - m_page->fillBox->setDisabled(true); - m_page->colorFillSelector->setDisabled(true); - m_page->sizeBox->setDisabled(true); - } m_options.lineDimension = cfg->getInt("lineDimension", 0); m_page->sizeBox->setCurrentIndex(m_options.lineDimension); connect(m_page, SIGNAL(colorSelectorChanged()), SLOT(setColorButton())); connect(m_page, SIGNAL(colorFillSelectorChanged()), SLOT(setColorFillButton())); connect(m_page->colorFillSelector, SIGNAL(changed(QColor)), SLOT(colorFillChanged(QColor))); connect(m_page->colorSelector, SIGNAL(changed(QColor)), SLOT(colorChanged(QColor))); - if (isVectorLayer) { - lockVectorLayerFunctions(); - } + m_page->enableControls(); + } KisDlgStrokeSelection::~KisDlgStrokeSelection() { - auto &m_options = m_page->m_options; + StrokeSelectionOptions &m_options = m_page->m_options; m_options.lineSize = m_page->lineSize->value(); m_options.lineDimension = m_page->sizeBox->currentIndex(); m_options.lineColorSource = m_page->lineColorBox->currentIndex(); KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration()); cfg->setProperty("lineSize", m_options.lineSize); cfg->setProperty("colorFillSource", m_options._colorFillSource); cfg->setProperty("useBrush", m_options.brushSelected); cfg->setProperty("lineDimension", m_options.lineDimension); cfg->setProperty("lineColorSource", m_options.lineColorSource); QVariant colorVariant("KoColor"); colorVariant.setValue(m_options.customColor); cfg->setProperty("customColor", colorVariant); QVariant _colorVariant("KoColor"); _colorVariant.setValue(m_options.color); cfg->setProperty("color", _colorVariant); QVariant _cVariant("KoColor"); _cVariant.setValue(m_options.fillColor); cfg->setProperty("fillColor", _cVariant); KisConfig(false).setExportConfiguration("StrokeSelection", cfg); delete m_page; } KoColor KisDlgStrokeSelection::getSelectedColor() const { KoColor color; QString currentSource = m_page->lineColorBox->currentText(); if (currentSource == "Foreground color") { color = m_resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value(); } else if (currentSource == "Background color") { - color = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value(); - } - else { - color = m_page->m_options.color; - } + color = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value(); + } + else { + color = m_page->m_options.color; + } return color; } KoColor KisDlgStrokeSelection::getFillSelectedColor() const { KoColor color; colorFillSource currentSource = static_cast(m_page->fillBox->currentIndex()); if (currentSource == colorFillSource::FGColor) { color = m_resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value(); } else if (currentSource == colorFillSource::BGColor) { - color = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value(); - } - else if (currentSource == colorFillSource::PaintColor) { - color = converter->approximateFromRenderedQColor(m_page->colorSelector->color()); - } - else { - color = m_page->m_options.customColor; - } + color = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value(); + } + else if (currentSource == colorFillSource::PaintColor) { + color = converter->approximateFromRenderedQColor(m_page->colorSelector->color()); + } + else { + color = m_page->m_options.customColor; + } return color; } bool KisDlgStrokeSelection::isBrushSelected() const { - int index = m_page->typeBox->currentIndex(); - drawType type = static_cast(index); - - if (type == drawType::brushDraw){ - return true; - } - else { - return false; - } + if (static_cast(m_page->typeBox->currentIndex()) == drawType::brushDraw){ + return true; + } + else { + return false; + } } StrokeSelectionOptions KisDlgStrokeSelection::getParams() const - { - StrokeSelectionOptions params; - - params.lineSize = getLineSize(); - params.color = getSelectedColor(); - params.brushSelected = isBrushSelected(); - params.fillColor = getFillSelectedColor(); - params._colorFillSource = m_page->m_options._colorFillSource; - return params; +{ + StrokeSelectionOptions params; -} + params.lineSize = getLineSize(); + params.color = getSelectedColor(); + params.brushSelected = isBrushSelected(); + params.fillColor = getFillSelectedColor(); + params._colorFillSource = m_page->m_options._colorFillSource; + return params; -void KisDlgStrokeSelection::lockVectorLayerFunctions() -{ - m_page->colorFillSelector->setEnabled(false); - m_page->lineSize->setEnabled(false); - m_page->sizeBox->setEnabled(false); - m_page->fillBox->setEnabled(false); - m_page->typeBox->setEnabled(false); } -void KisDlgStrokeSelection::unlockVectorLayerFunctions() -{ - m_page->colorFillSelector->setEnabled(true); - m_page->lineSize->setEnabled(true); - m_page->sizeBox->setEnabled(true); - m_page->fillBox->setEnabled(true); - m_page->typeBox->setEnabled(true); -} void KisDlgStrokeSelection::setColorFillButton() { m_page->colorFillSelector->setColor(getFillSelectedColor().toQColor()); } void KisDlgStrokeSelection::setColorButton() { m_page->colorSelector->setColor(getSelectedColor().toQColor()); } int KisDlgStrokeSelection::getLineSize() const { int value = m_page->lineSize->value(); if (m_page->sizeBox->currentText() == i18n("px")) { return value; } else if (m_page->sizeBox->currentText() == i18n("mm")) { - int pixels = static_cast(KoUnit::convertFromUnitToUnit(value,KoUnit(KoUnit::Millimeter), KoUnit(KoUnit::Pixel))); - return pixels; + int pixels = static_cast(KoUnit::convertFromUnitToUnit(value,KoUnit(KoUnit::Millimeter), KoUnit(KoUnit::Pixel))); + return pixels; } - else { - int pixels = static_cast(KoUnit::convertFromUnitToUnit(value, KoUnit(KoUnit::Inch), KoUnit(KoUnit::Pixel))); - return pixels; + else { + int pixels = static_cast(KoUnit::convertFromUnitToUnit(value, KoUnit(KoUnit::Inch), KoUnit(KoUnit::Pixel))); + return pixels; } } linePosition KisDlgStrokeSelection::getLinePosition() const {/* TODO int index = m_page->linePosition->currentIndex(); switch(index) { case(0): return linePosition::OUTSIDE; case(1): return linePosition::INSIDE; case(2): return linePosition::CENTER; default: return linePosition::CENTER; }*/ return linePosition::CENTER; } void KisDlgStrokeSelection::colorChanged(const QColor &newColor) { if (m_page->fillBox->currentText() == "Paint color") { m_page->colorFillSelector->setColor(newColor); } QColor BGColor = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value().toQColor(); QColor FGColor = m_resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value().toQColor(); KoColor tempColor= converter->approximateFromRenderedQColor(newColor); - if (!(newColor == BGColor) && !(newColor == FGColor)) { + if (!(newColor == BGColor) && !(newColor == FGColor)) { m_page->m_options.color = tempColor; m_page->lineColorBox->setCurrentIndex(2); //custom color - } + } } void KisDlgStrokeSelection::colorFillChanged(const QColor &newColor) { QColor PaintColor = m_page->colorSelector->color(); QColor BGcolor = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value().toQColor(); QColor FGColor = m_resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value().toQColor(); KoColor tempColor= converter->approximateFromRenderedQColor(newColor); if (!(newColor == FGColor) && !(newColor == BGcolor) && !(newColor == PaintColor)) { m_page->m_options.customColor = tempColor; m_page->fillBox->setCurrentIndex(static_cast(colorFillSource::CustomColor)); } m_page->m_options.fillColor = tempColor; } WdgStrokeSelection::WdgStrokeSelection(QWidget *parent) : QWidget(parent) { setupUi(this); } +void WdgStrokeSelection::enableControls() +{ + m_options.fillColor = m_cfg->getColor("fillColor"); + if (m_options._colorFillSource == static_cast(colorFillSource::None)) { + colorFillSelector->setEnabled(false); + } + else { + colorFillSelector->setEnabled(true); + } + + if (m_isVectorLayer) { + typeBox->setCurrentIndex(1); + typeBox->setEnabled(false); + } + else { + typeBox->setEnabled(true); + } + + on_typeBox_currentIndexChanged(typeBox->currentIndex()); +} + void WdgStrokeSelection::on_fillBox_currentIndexChanged(int index) { if (index == static_cast(colorFillSource::None)) { colorFillSelector->setDisabled(true); } else { colorFillSelector->setDisabled(false); emit colorFillSelectorChanged(); } m_options._colorFillSource = index; } -void WdgStrokeSelection::on_typeBox_currentIndexChanged(const QString &arg1) +void WdgStrokeSelection::on_typeBox_currentIndexChanged(int arg1) { - if (arg1 == "Current Brush") { - m_options.brushSelected = true; - lineSize->setDisabled(true); - fillBox->setDisabled(true); - colorFillSelector->setDisabled(true); - sizeBox->setDisabled(true); - } - else { - m_options.brushSelected = false; - lineSize->setDisabled(false); - fillBox->setDisabled(false); - colorFillSelector->setDisabled(false); - sizeBox->setDisabled(false); - } + if (arg1 == 0) { + m_options.brushSelected = true; + lineSize->setEnabled(false); + fillBox->setEnabled(false); + colorFillSelector->setEnabled(false); + sizeBox->setEnabled(false); + } + else { + m_options.brushSelected = false; + lineSize->setEnabled(true); + fillBox->setEnabled(true); + colorFillSelector->setEnabled(true); + sizeBox->setEnabled(true); + } } -void WdgStrokeSelection::on_lineColorBox_currentIndexChanged(const QString &/*arg1*/) +void WdgStrokeSelection::on_lineColorBox_currentIndexChanged(int/*arg1*/) { emit colorSelectorChanged(); } StrokeSelectionOptions ::StrokeSelectionOptions(): lineSize(1), brushSelected(false), _colorFillSource(0), lineColorSource(0), lineDimension(0) { color.fromQColor(Qt::black); fillColor.fromQColor(Qt::black); customColor.fromQColor(Qt::black); } KisToolShapeUtils::FillStyle StrokeSelectionOptions::fillStyle() const { using namespace KisToolShapeUtils; colorFillSource tempColor = static_cast(_colorFillSource); FillStyle style = FillStyleNone; switch (tempColor) { case colorFillSource::PaintColor: style = FillStyleForegroundColor; break; case colorFillSource::BGColor: style = FillStyleBackgroundColor; break; case colorFillSource::CustomColor: style = FillStyleBackgroundColor; break; case colorFillSource::None: style = FillStyleNone; break; case colorFillSource::FGColor: style = FillStyleBackgroundColor; break; } return style; } diff --git a/libs/ui/dialogs/kis_dlg_stroke_selection_properties.h b/libs/ui/dialogs/kis_dlg_stroke_selection_properties.h index b2f8d6ae02..a866957a8d 100644 --- a/libs/ui/dialogs/kis_dlg_stroke_selection_properties.h +++ b/libs/ui/dialogs/kis_dlg_stroke_selection_properties.h @@ -1,113 +1,117 @@ /* * Copyright (c) 2016 Alexey Kapustin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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_DLG_STROKE_SELECTION_PROPERTIES_H_ #define KIS_DLG_STROKE_SELECTION_PROPERTIES_H_ #include #include "KisProofingConfiguration.h" #include #include "KisViewManager.h" #include "KoStrokeConfigWidget.h" #include "ui_wdgstrokeselectionproperties.h" #include #include class KoColorSpace; class KoColorPopupAction; enum class linePosition { OUTSIDE, INSIDE, CENTER }; enum class drawType{ brushDraw, lineDraw }; enum class colorFillSource { None, PaintColor, BGColor, CustomColor, FGColor }; struct StrokeSelectionOptions { StrokeSelectionOptions (); int lineSize; bool brushSelected; int _colorFillSource; int lineColorSource; int lineDimension; KoColor color; KoColor fillColor; KoColor customColor; KisToolShapeUtils::FillStyle fillStyle() const; void lock(); }; class WdgStrokeSelection : public QWidget, public Ui::WdgStrokeSelection { Q_OBJECT public: WdgStrokeSelection(QWidget *parent) ; StrokeSelectionOptions m_options; + bool m_isVectorLayer; + KisPropertiesConfigurationSP m_cfg; + + void enableControls(); + Q_SIGNALS: void colorFillSelectorChanged(); void colorSelectorChanged(); private Q_SLOTS: void on_fillBox_currentIndexChanged(int index); - void on_typeBox_currentIndexChanged(const QString &arg1); - void on_lineColorBox_currentIndexChanged(const QString &arg1); + void on_typeBox_currentIndexChanged(int index); + void on_lineColorBox_currentIndexChanged(int index); }; class KisDlgStrokeSelection : public KoDialog { Q_OBJECT - public: KisDlgStrokeSelection(KisImageWSP image, KisViewManager *view, bool isVectorLayer); ~KisDlgStrokeSelection() override; + int getLineSize() const; linePosition getLinePosition() const; KoColor getSelectedColor() const; bool isBrushSelected() const; KoColor getFillSelectedColor() const; StrokeSelectionOptions getParams() const; - void lockVectorLayerFunctions(); - void unlockVectorLayerFunctions(); private: - WdgStrokeSelection * m_page; + WdgStrokeSelection *m_page {0}; KisImageWSP m_image; - KoCanvasResourceProvider *m_resourceManager; - KisDisplayColorConverter *converter; + KoCanvasResourceProvider *m_resourceManager {0}; + KisDisplayColorConverter *converter {0}; + bool m_isVectorLayer {false}; private Q_SLOTS: void setColorFillButton(); void setColorButton(); void colorChanged(const QColor &newColor); void colorFillChanged(const QColor &newColor); }; #endif // KIS_DLG_STROKE_SEL_PROPERTIES_H_ diff --git a/libs/ui/forms/wdgstrokeselectionproperties.ui b/libs/ui/forms/wdgstrokeselectionproperties.ui index 1f258c5e65..6641d1ecd1 100644 --- a/libs/ui/forms/wdgstrokeselectionproperties.ui +++ b/libs/ui/forms/wdgstrokeselectionproperties.ui @@ -1,210 +1,248 @@ WdgStrokeSelection 0 0 - 304 + 334 208 0 0 New Image - + 0 Stroke - - - - - - - 0 - - - - Current Brush - - - - - Line selection - - - + + + + + Type: + + + + + + + + 0 + 0 + + + + + 175 + 0 + + + + 0 + + + + Current Brush + + + + + Line selection + + + + + + + + Line: + + + + + + + + 0 + 0 + + + + + 175 + 0 + + + + + Foreground color + - - - - Fill: - - + + + Background color + - - - - Type: - - + + + Custom color + - - - - - - - 1 - - - 1000000 - - - 1 - - + + + + + + Color + + + false + + + false + + + false + + + false + + + + + + + Width: + + + lineSize + + + + + + + + 175 + 0 + + + + + + + 1 + + + 1000000 + + + 1 + + + + + + + + px + - - - - Width: - - - lineSize - - + + + mm + - - - - - px - - - - - mm - - - - - inch - - - + + + inch + - - - - - None - - - - - Paint color - - - - - Background color - - - - - Custom color - - - - - Foreground color - - - + + + + + + Fill: + + + + + + + + 0 + 0 + + + + + 175 + 0 + + + + + None + - - - - Line: - - + + + Paint color + - - - - Color - - - false - - - false - - - false - - - false - - + + + Background color + - - - - - Foreground color - - - - - Background color - - - - - Custom color - - - + + + Custom color + - - - - Color - - + + + Foreground color + - + + + + + + Color + + KColorButton QPushButton
      kcolorbutton.h
      1
      diff --git a/libs/ui/input/wintab/kis_tablet_support_win_p.h b/libs/ui/input/wintab/kis_tablet_support_win_p.h index 64a8da3ea1..303948b3ac 100644 --- a/libs/ui/input/wintab/kis_tablet_support_win_p.h +++ b/libs/ui/input/wintab/kis_tablet_support_win_p.h @@ -1,146 +1,146 @@ /* * Copyright (C) 2015 The Qt Company Ltd. - * Contact: http://www.qt.io/licensing/ + * Contact: https://www.qt.io/licensing/ * Copyright (C) 2015 Michael Abrahms * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TABLET_SUPPORT_WIN_P_H #define KIS_TABLET_SUPPORT_WIN_P_H #include #include #include #include #include "wintab.h" QT_BEGIN_NAMESPACE class QDebug; class QWindow; class QRect; class QWidget; struct QWindowsWinTab32DLL { QWindowsWinTab32DLL() : wTOpen(0), wTClose(0), wTInfo(0), wTEnable(0), wTOverlap(0), wTPacketsGet(0), wTGet(0), wTQueueSizeGet(0), wTQueueSizeSet(0) {} bool init(); typedef HCTX (API *PtrWTOpen)(HWND, LPLOGCONTEXT, BOOL); typedef BOOL (API *PtrWTClose)(HCTX); typedef UINT (API *PtrWTInfo)(UINT, UINT, LPVOID); typedef BOOL (API *PtrWTEnable)(HCTX, BOOL); typedef BOOL (API *PtrWTOverlap)(HCTX, BOOL); typedef int (API *PtrWTPacketsGet)(HCTX, int, LPVOID); typedef BOOL (API *PtrWTGet)(HCTX, LPLOGCONTEXT); typedef int (API *PtrWTQueueSizeGet)(HCTX); typedef BOOL (API *PtrWTQueueSizeSet)(HCTX, int); PtrWTOpen wTOpen; PtrWTClose wTClose; PtrWTInfo wTInfo; PtrWTEnable wTEnable; PtrWTOverlap wTOverlap; PtrWTPacketsGet wTPacketsGet; PtrWTGet wTGet; PtrWTQueueSizeGet wTQueueSizeGet; PtrWTQueueSizeSet wTQueueSizeSet; }; struct QWindowsTabletDeviceData { QWindowsTabletDeviceData() : minPressure(0), maxPressure(0), minTanPressure(0), maxTanPressure(0), minX(0), maxX(0), minY(0), maxY(0), minZ(0), maxZ(0), uniqueId(0), currentDevice(0), currentPointerType(0) {} QPointF scaleCoordinates(int coordX, int coordY,const QRect &targetArea) const; qreal scalePressure(qreal p) const { return p / qreal(maxPressure - minPressure); } qreal scaleTangentialPressure(qreal p) const { return p / qreal(maxTanPressure - minTanPressure); } int minPressure; int maxPressure; int minTanPressure; int maxTanPressure; int minX, maxX, minY, maxY, minZ, maxZ; qint64 uniqueId; int currentDevice; int currentPointerType; QRect virtualDesktopArea; // Added by Krita QMap buttonsMap; }; QDebug operator<<(QDebug d, const QWindowsTabletDeviceData &t); class QWindowsTabletSupport { Q_DISABLE_COPY(QWindowsTabletSupport) explicit QWindowsTabletSupport(HWND window, HCTX context); public: ~QWindowsTabletSupport(); static QWindowsTabletSupport *create(); void notifyActivate(); QString description() const; bool translateTabletProximityEvent(WPARAM wParam, LPARAM lParam); bool translateTabletPacketEvent(); int absoluteRange() const { return m_absoluteRange; } void setAbsoluteRange(int a) { m_absoluteRange = a; } void tabletUpdateCursor(const int pkCursor); static QWindowsWinTab32DLL m_winTab32DLL; private: unsigned options() const; QWindowsTabletDeviceData tabletInit(const quint64 uniqueId, const UINT cursorType) const; const HWND m_window; const HCTX m_context; int m_absoluteRange; bool m_tiltSupport; QVector m_devices; int m_currentDevice; QWidget *targetWidget{0}; /** * This is an inelegant solution to record pen / eraser switches. * On the Surface Pro 3 we are only notified of cursor changes at the last minute. * The recommended way to handle switches is WT_CSRCHANGE, but that doesn't work * unless we save packet ID information, and we cannot change the structure of the * PACKETDATA due to Qt restrictions. * * Furthermore, WT_CSRCHANGE only ever appears *after* we receive the packet. */ UINT currentPkCursor{0}; bool isSurfacePro3{false}; //< Only enable this on SP3 or other devices with the same issue. }; QT_END_NAMESPACE #endif // KIS_TABLET_SUPPORT_WIN_P_H diff --git a/libs/ui/kis_config.cc b/libs/ui/kis_config.cc index 4c7711b248..dc2d10931a 100644 --- a/libs/ui/kis_config.cc +++ b/libs/ui/kis_config.cc @@ -1,2209 +1,2215 @@ /* * Copyright (c) 2002 Patrick Julien * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas_resource_provider.h" #include "kis_config_notifier.h" #include "kis_snap_config.h" #include #include #include #include #include #ifdef Q_OS_WIN #include "config_use_qt_tablet_windows.h" #endif KisConfig::KisConfig(bool readOnly) : m_cfg( KSharedConfig::openConfig()->group("")) , m_readOnly(readOnly) { if (!readOnly) { KIS_SAFE_ASSERT_RECOVER_RETURN(qApp && qApp->thread() == QThread::currentThread()); } } KisConfig::~KisConfig() { if (m_readOnly) return; if (qApp && qApp->thread() != QThread::currentThread()) { dbgKrita << "WARNING: KisConfig: requested config synchronization from nonGUI thread! Called from:" << kisBacktrace(); return; } m_cfg.sync(); } void KisConfig::logImportantSettings() const { - KisUsageLogger::write("Current Settings\n"); - KisUsageLogger::write(QString("\tCurrent Swap Location: %1").arg(KisImageConfig(true).swapDir())); - KisUsageLogger::write(QString("\tUndo Enabled: %1").arg(undoEnabled())); - KisUsageLogger::write(QString("\tUndo Stack Limit: %1").arg(undoStackLimit())); - KisUsageLogger::write(QString("\tUse OpenGL: %1").arg(useOpenGL())); - KisUsageLogger::write(QString("\tUse OpenGL Texture Buffer: %1").arg(useOpenGLTextureBuffer())); - KisUsageLogger::write(QString("\tUse AMD Vectorization Workaround: %1").arg(enableAmdVectorizationWorkaround())); - KisUsageLogger::write(QString("\tCanvas State: %1").arg(canvasState())); - KisUsageLogger::write(QString("\tAutosave Interval: %1").arg(autoSaveInterval())); - KisUsageLogger::write(QString("\tUse Backup Files: %1").arg(backupFile())); - KisUsageLogger::write(QString("\tNumber of Backups Kept: %1").arg(m_cfg.readEntry("numberofbackupfiles", 1))); - KisUsageLogger::write(QString("\tBackup File Suffix: %1").arg(m_cfg.readEntry("backupfilesuffix", "~"))); + KisUsageLogger::writeSysInfo("Current Settings\n"); + KisUsageLogger::writeSysInfo(QString(" Current Swap Location: %1").arg(KisImageConfig(true).swapDir())); + KisUsageLogger::writeSysInfo(QString(" Undo Enabled: %1").arg(undoEnabled())); + KisUsageLogger::writeSysInfo(QString(" Undo Stack Limit: %1").arg(undoStackLimit())); + KisUsageLogger::writeSysInfo(QString(" Use OpenGL: %1").arg(useOpenGL())); + KisUsageLogger::writeSysInfo(QString(" Use OpenGL Texture Buffer: %1").arg(useOpenGLTextureBuffer())); + KisUsageLogger::writeSysInfo(QString(" Use AMD Vectorization Workaround: %1").arg(enableAmdVectorizationWorkaround())); + 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())); + 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::write(QString("\tBackup Location: %1").arg(backupDir)); + KisUsageLogger::writeSysInfo(QString(" Backup Location: %1").arg(backupDir)); - KisUsageLogger::write(QString("\tUse Win8 Pointer Input: %1").arg(useWin8PointerInput())); - KisUsageLogger::write(QString("\tUse RightMiddleTabletButton Workaround: %1").arg(useRightMiddleTabletButtonWorkaround())); - KisUsageLogger::write(QString("\tLevels of Detail Enabled: %1").arg(levelOfDetailEnabled())); - KisUsageLogger::write(QString("\tUse Zip64: %1").arg(useZip64())); + KisUsageLogger::writeSysInfo(QString(" Use Win8 Pointer Input: %1").arg(useWin8PointerInput())); + KisUsageLogger::writeSysInfo(QString(" Use RightMiddleTabletButton Workaround: %1").arg(useRightMiddleTabletButtonWorkaround())); + KisUsageLogger::writeSysInfo(QString(" Levels of Detail Enabled: %1").arg(levelOfDetailEnabled())); + KisUsageLogger::writeSysInfo(QString(" Use Zip64: %1").arg(useZip64())); - KisUsageLogger::write("\n"); + 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); } bool KisConfig::useOpenGLTextureBuffer(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useOpenGLTextureBuffer", true)); } void KisConfig::setUseOpenGLTextureBuffer(bool useBuffer) { m_cfg.writeEntry("useOpenGLTextureBuffer", useBuffer); } int KisConfig::openGLTextureSize(bool defaultValue) const { return (defaultValue ? 256 : m_cfg.readEntry("textureSize", 256)); } bool KisConfig::disableVSync(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("disableVSync", true)); } void KisConfig::setDisableVSync(bool disableVSync) { m_cfg.writeEntry("disableVSync", disableVSync); } bool KisConfig::showAdvancedOpenGLSettings(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showAdvancedOpenGLSettings", false)); } bool KisConfig::forceOpenGLFenceWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceOpenGLFenceWorkaround", false)); } int KisConfig::numMipmapLevels(bool defaultValue) const { return (defaultValue ? 4 : m_cfg.readEntry("numMipmapLevels", 4)); } int KisConfig::textureOverlapBorder() const { return 1 << qMax(0, numMipmapLevels()); } quint32 KisConfig::getGridMainStyle(bool defaultValue) const { int v = m_cfg.readEntry("gridmainstyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGridMainStyle(quint32 v) const { m_cfg.writeEntry("gridmainstyle", v); } quint32 KisConfig::getGridSubdivisionStyle(bool defaultValue) const { quint32 v = m_cfg.readEntry("gridsubdivisionstyle", 1); if (v > 2) v = 2; return (defaultValue ? 1 : v); } void KisConfig::setGridSubdivisionStyle(quint32 v) const { m_cfg.writeEntry("gridsubdivisionstyle", v); } QColor KisConfig::getGridMainColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("gridmaincolor", col)); } void KisConfig::setGridMainColor(const QColor & v) const { m_cfg.writeEntry("gridmaincolor", v); } QColor KisConfig::getGridSubdivisionColor(bool defaultValue) const { QColor col(150, 150, 150); return (defaultValue ? col : m_cfg.readEntry("gridsubdivisioncolor", col)); } void KisConfig::setGridSubdivisionColor(const QColor & v) const { m_cfg.writeEntry("gridsubdivisioncolor", v); } QColor KisConfig::getPixelGridColor(bool defaultValue) const { QColor col(255, 255, 255); return (defaultValue ? col : m_cfg.readEntry("pixelGridColor", col)); } void KisConfig::setPixelGridColor(const QColor & v) const { m_cfg.writeEntry("pixelGridColor", v); } qreal KisConfig::getPixelGridDrawingThreshold(bool defaultValue) const { qreal border = 24.0f; return (defaultValue ? border : m_cfg.readEntry("pixelGridDrawingThreshold", border)); } void KisConfig::setPixelGridDrawingThreshold(qreal v) const { m_cfg.writeEntry("pixelGridDrawingThreshold", v); } bool KisConfig::pixelGridEnabled(bool defaultValue) const { bool enabled = true; return (defaultValue ? enabled : m_cfg.readEntry("pixelGridEnabled", enabled)); } void KisConfig::enablePixelGrid(bool v) const { m_cfg.writeEntry("pixelGridEnabled", v); } quint32 KisConfig::guidesLineStyle(bool defaultValue) const { int v = m_cfg.readEntry("guidesLineStyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGuidesLineStyle(quint32 v) const { m_cfg.writeEntry("guidesLineStyle", v); } QColor KisConfig::guidesColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("guidesColor", col)); } void KisConfig::setGuidesColor(const QColor & v) const { m_cfg.writeEntry("guidesColor", v); } void KisConfig::loadSnapConfig(KisSnapConfig *config, bool defaultValue) const { KisSnapConfig defaultConfig(false); if (defaultValue) { *config = defaultConfig; return; } config->setOrthogonal(m_cfg.readEntry("globalSnapOrthogonal", defaultConfig.orthogonal())); config->setNode(m_cfg.readEntry("globalSnapNode", defaultConfig.node())); config->setExtension(m_cfg.readEntry("globalSnapExtension", defaultConfig.extension())); config->setIntersection(m_cfg.readEntry("globalSnapIntersection", defaultConfig.intersection())); config->setBoundingBox(m_cfg.readEntry("globalSnapBoundingBox", defaultConfig.boundingBox())); config->setImageBounds(m_cfg.readEntry("globalSnapImageBounds", defaultConfig.imageBounds())); config->setImageCenter(m_cfg.readEntry("globalSnapImageCenter", defaultConfig.imageCenter())); config->setToPixel(m_cfg.readEntry("globalSnapToPixel", defaultConfig.toPixel())); } void KisConfig::saveSnapConfig(const KisSnapConfig &config) { m_cfg.writeEntry("globalSnapOrthogonal", config.orthogonal()); m_cfg.writeEntry("globalSnapNode", config.node()); m_cfg.writeEntry("globalSnapExtension", config.extension()); m_cfg.writeEntry("globalSnapIntersection", config.intersection()); m_cfg.writeEntry("globalSnapBoundingBox", config.boundingBox()); m_cfg.writeEntry("globalSnapImageBounds", config.imageBounds()); m_cfg.writeEntry("globalSnapImageCenter", config.imageCenter()); m_cfg.writeEntry("globalSnapToPixel", config.toPixel()); } qint32 KisConfig::checkSize(bool defaultValue) const { qint32 size = (defaultValue ? 32 : m_cfg.readEntry("checksize", 32)); if (size == 0) size = 32; return size; } void KisConfig::setCheckSize(qint32 checksize) const { if (checksize == 0) { checksize = 32; } m_cfg.writeEntry("checksize", checksize); } bool KisConfig::scrollCheckers(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("scrollingcheckers", false)); } void KisConfig::setScrollingCheckers(bool sc) const { m_cfg.writeEntry("scrollingcheckers", sc); } QColor KisConfig::canvasBorderColor(bool defaultValue) const { QColor color(QColor(128,128,128)); return (defaultValue ? color : m_cfg.readEntry("canvasBorderColor", color)); } void KisConfig::setCanvasBorderColor(const QColor& color) const { m_cfg.writeEntry("canvasBorderColor", color); } bool KisConfig::hideScrollbars(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("hideScrollbars", false)); } void KisConfig::setHideScrollbars(bool value) const { m_cfg.writeEntry("hideScrollbars", value); } QColor KisConfig::checkersColor1(bool defaultValue) const { QColor col(220, 220, 220); return (defaultValue ? col : m_cfg.readEntry("checkerscolor", col)); } void KisConfig::setCheckersColor1(const QColor & v) const { m_cfg.writeEntry("checkerscolor", v); } QColor KisConfig::checkersColor2(bool defaultValue) const { return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("checkerscolor2", QColor(Qt::white))); } void KisConfig::setCheckersColor2(const QColor & v) const { m_cfg.writeEntry("checkerscolor2", v); } bool KisConfig::antialiasCurves(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("antialiascurves", true)); } void KisConfig::setAntialiasCurves(bool v) const { m_cfg.writeEntry("antialiascurves", v); } bool KisConfig::antialiasSelectionOutline(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("AntialiasSelectionOutline", false)); } void KisConfig::setAntialiasSelectionOutline(bool v) const { m_cfg.writeEntry("AntialiasSelectionOutline", v); } bool KisConfig::showRootLayer(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowRootLayer", false)); } void KisConfig::setShowRootLayer(bool showRootLayer) const { m_cfg.writeEntry("ShowRootLayer", showRootLayer); } bool KisConfig::showGlobalSelection(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowGlobalSelection", false)); } void KisConfig::setShowGlobalSelection(bool showGlobalSelection) const { m_cfg.writeEntry("ShowGlobalSelection", showGlobalSelection); } bool KisConfig::showOutlineWhilePainting(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ShowOutlineWhilePainting", true)); } void KisConfig::setShowOutlineWhilePainting(bool showOutlineWhilePainting) const { m_cfg.writeEntry("ShowOutlineWhilePainting", showOutlineWhilePainting); } bool KisConfig::forceAlwaysFullSizedOutline(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceAlwaysFullSizedOutline", false)); } void KisConfig::setForceAlwaysFullSizedOutline(bool value) const { m_cfg.writeEntry("forceAlwaysFullSizedOutline", value); } KisConfig::SessionOnStartup KisConfig::sessionOnStartup(bool defaultValue) const { int value = defaultValue ? SOS_BlankSession : m_cfg.readEntry("sessionOnStartup", (int)SOS_BlankSession); return (KisConfig::SessionOnStartup)value; } void KisConfig::setSessionOnStartup(SessionOnStartup value) { m_cfg.writeEntry("sessionOnStartup", (int)value); } bool KisConfig::saveSessionOnQuit(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("saveSessionOnQuit", false); } void KisConfig::setSaveSessionOnQuit(bool value) { m_cfg.writeEntry("saveSessionOnQuit", value); } qreal KisConfig::outlineSizeMinimum(bool defaultValue) const { return (defaultValue ? 1.0 : m_cfg.readEntry("OutlineSizeMinimum", 1.0)); } void KisConfig::setOutlineSizeMinimum(qreal outlineSizeMinimum) const { m_cfg.writeEntry("OutlineSizeMinimum", outlineSizeMinimum); } qreal KisConfig::selectionViewSizeMinimum(bool defaultValue) const { return (defaultValue ? 5.0 : m_cfg.readEntry("SelectionViewSizeMinimum", 5.0)); } void KisConfig::setSelectionViewSizeMinimum(qreal outlineSizeMinimum) const { m_cfg.writeEntry("SelectionViewSizeMinimum", outlineSizeMinimum); } int KisConfig::autoSaveInterval(bool defaultValue) const { return (defaultValue ? 15 * 60 : m_cfg.readEntry("AutoSaveInterval", 15 * 60)); } void KisConfig::setAutoSaveInterval(int seconds) const { return m_cfg.writeEntry("AutoSaveInterval", seconds); } bool KisConfig::backupFile(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("CreateBackupFile", true)); } void KisConfig::setBackupFile(bool backupFile) const { m_cfg.writeEntry("CreateBackupFile", backupFile); } bool KisConfig::showFilterGallery(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showFilterGallery", false)); } void KisConfig::setShowFilterGallery(bool showFilterGallery) const { m_cfg.writeEntry("showFilterGallery", showFilterGallery); } bool KisConfig::showFilterGalleryLayerMaskDialog(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showFilterGalleryLayerMaskDialog", true)); } void KisConfig::setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const { m_cfg.writeEntry("setShowFilterGalleryLayerMaskDialog", showFilterGallery); } QString KisConfig::canvasState(bool defaultValue) const { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return (defaultValue ? "OPENGL_NOT_TRIED" : kritarc.value("canvasState", "OPENGL_NOT_TRIED").toString()); } void KisConfig::setCanvasState(const QString& state) const { static QStringList acceptableStates; if (acceptableStates.isEmpty()) { acceptableStates << "OPENGL_SUCCESS" << "TRY_OPENGL" << "OPENGL_NOT_TRIED" << "OPENGL_FAILED"; } if (acceptableStates.contains(state)) { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", state); } } bool KisConfig::toolOptionsPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ToolOptionsPopupDetached", false)); } void KisConfig::setToolOptionsPopupDetached(bool detached) const { m_cfg.writeEntry("ToolOptionsPopupDetached", detached); } bool KisConfig::paintopPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("PaintopPopupDetached", false)); } void KisConfig::setPaintopPopupDetached(bool detached) const { m_cfg.writeEntry("PaintopPopupDetached", detached); } QString KisConfig::pressureTabletCurve(bool defaultValue) const { return (defaultValue ? "0,0;1,1" : m_cfg.readEntry("tabletPressureCurve","0,0;1,1;")); } void KisConfig::setPressureTabletCurve(const QString& curveString) const { m_cfg.writeEntry("tabletPressureCurve", curveString); } bool KisConfig::useWin8PointerInput(bool defaultValue) const { #ifdef Q_OS_WIN #ifdef USE_QT_TABLET_WINDOWS const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return useWin8PointerInputNoApp(&kritarc, defaultValue); #else return (defaultValue ? false : m_cfg.readEntry("useWin8PointerInput", false)); #endif #else Q_UNUSED(defaultValue); return false; #endif } void KisConfig::setUseWin8PointerInput(bool value) { #ifdef Q_OS_WIN // Special handling: Only set value if changed // I don't want it to be set if the user hasn't touched it if (useWin8PointerInput() != value) { #ifdef USE_QT_TABLET_WINDOWS const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); setUseWin8PointerInputNoApp(&kritarc, value); #else m_cfg.writeEntry("useWin8PointerInput", value); #endif } #else Q_UNUSED(value) #endif } bool KisConfig::useWin8PointerInputNoApp(QSettings *settings, bool defaultValue) { return defaultValue ? false : settings->value("useWin8PointerInput", false).toBool(); } void KisConfig::setUseWin8PointerInputNoApp(QSettings *settings, bool value) { settings->setValue("useWin8PointerInput", value); } bool KisConfig::useRightMiddleTabletButtonWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useRightMiddleTabletButtonWorkaround", false)); } void KisConfig::setUseRightMiddleTabletButtonWorkaround(bool value) { m_cfg.writeEntry("useRightMiddleTabletButtonWorkaround", value); } qreal KisConfig::vastScrolling(bool defaultValue) const { return (defaultValue ? 0.9 : m_cfg.readEntry("vastScrolling", 0.9)); } void KisConfig::setVastScrolling(const qreal factor) const { m_cfg.writeEntry("vastScrolling", factor); } int KisConfig::presetChooserViewMode(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("presetChooserViewMode", 0)); } void KisConfig::setPresetChooserViewMode(const int mode) const { m_cfg.writeEntry("presetChooserViewMode", mode); } int KisConfig::presetIconSize(bool defaultValue) const { return (defaultValue ? 60 : m_cfg.readEntry("presetIconSize", 60)); } void KisConfig::setPresetIconSize(const int value) const { m_cfg.writeEntry("presetIconSize", value); } bool KisConfig::firstRun(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("firstRun", true)); } void KisConfig::setFirstRun(const bool first) const { m_cfg.writeEntry("firstRun", first); } int KisConfig::horizontalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("horizontalSplitLines", 1)); } void KisConfig::setHorizontalSplitLines(const int numberLines) const { m_cfg.writeEntry("horizontalSplitLines", numberLines); } int KisConfig::verticalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("verticalSplitLines", 1)); } void KisConfig::setVerticalSplitLines(const int numberLines) const { m_cfg.writeEntry("verticalSplitLines", numberLines); } bool KisConfig::clicklessSpacePan(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("clicklessSpacePan", true)); } void KisConfig::setClicklessSpacePan(const bool toggle) const { m_cfg.writeEntry("clicklessSpacePan", toggle); } bool KisConfig::hideDockersFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideDockersFullScreen", true)); } void KisConfig::setHideDockersFullscreen(const bool value) const { m_cfg.writeEntry("hideDockersFullScreen", value); } bool KisConfig::showDockers(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showDockers", true)); } void KisConfig::setShowDockers(const bool value) const { m_cfg.writeEntry("showDockers", value); } bool KisConfig::showStatusBar(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showStatusBar", true)); } void KisConfig::setShowStatusBar(const bool value) const { m_cfg.writeEntry("showStatusBar", value); } bool KisConfig::hideMenuFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideMenuFullScreen", true)); } void KisConfig::setHideMenuFullscreen(const bool value) const { m_cfg.writeEntry("hideMenuFullScreen", value); } bool KisConfig::hideScrollbarsFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideScrollbarsFullScreen", true)); } void KisConfig::setHideScrollbarsFullscreen(const bool value) const { m_cfg.writeEntry("hideScrollbarsFullScreen", value); } bool KisConfig::hideStatusbarFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideStatusbarFullScreen", true)); } void KisConfig::setHideStatusbarFullscreen(const bool value) const { m_cfg.writeEntry("hideStatusbarFullScreen", value); } bool KisConfig::hideTitlebarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideTitleBarFullscreen", true)); } void KisConfig::setHideTitlebarFullscreen(const bool value) const { m_cfg.writeEntry("hideTitleBarFullscreen", value); } bool KisConfig::hideToolbarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideToolbarFullscreen", true)); } void KisConfig::setHideToolbarFullscreen(const bool value) const { m_cfg.writeEntry("hideToolbarFullscreen", value); } bool KisConfig::fullscreenMode(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("fullscreenMode", true)); } void KisConfig::setFullscreenMode(const bool value) const { m_cfg.writeEntry("fullscreenMode", value); } QStringList KisConfig::favoriteCompositeOps(bool defaultValue) const { return (defaultValue ? QStringList() : m_cfg.readEntry("favoriteCompositeOps", QString("normal,erase,multiply,burn,darken,add,dodge,screen,overlay,soft_light_svg,luminize,lighten,saturation,color,divide").split(','))); } void KisConfig::setFavoriteCompositeOps(const QStringList& compositeOps) const { m_cfg.writeEntry("favoriteCompositeOps", compositeOps); } QString KisConfig::exportConfigurationXML(const QString &filterId, bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("ExportConfiguration-" + filterId, QString())); } KisPropertiesConfigurationSP KisConfig::exportConfiguration(const QString &filterId, bool defaultValue) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); const QString xmlData = exportConfigurationXML(filterId, defaultValue); cfg->fromXML(xmlData); return cfg; } void KisConfig::setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const { QString exportConfig = properties->toXML(); m_cfg.writeEntry("ExportConfiguration-" + filterId, exportConfig); } QString KisConfig::importConfiguration(const QString &filterId, bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("ImportConfiguration-" + filterId, QString())); } void KisConfig::setImportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const { QString importConfig = properties->toXML(); m_cfg.writeEntry("ImportConfiguration-" + filterId, importConfig); } bool KisConfig::useOcio(bool defaultValue) const { #ifdef HAVE_OCIO return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/UseOcio", false)); #else Q_UNUSED(defaultValue); return false; #endif } void KisConfig::setUseOcio(bool useOCIO) const { m_cfg.writeEntry("Krita/Ocio/UseOcio", useOCIO); } int KisConfig::favoritePresets(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("numFavoritePresets", 10)); } void KisConfig::setFavoritePresets(const int value) { m_cfg.writeEntry("numFavoritePresets", value); } bool KisConfig::levelOfDetailEnabled(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("levelOfDetailEnabled", false)); } void KisConfig::setLevelOfDetailEnabled(bool value) { m_cfg.writeEntry("levelOfDetailEnabled", value); } KisOcioConfiguration KisConfig::ocioConfiguration(bool defaultValue) const { KisOcioConfiguration cfg; if (!defaultValue) { cfg.mode = (KisOcioConfiguration::Mode)m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", 0); cfg.configurationPath = m_cfg.readEntry("Krita/Ocio/OcioConfigPath", QString()); cfg.lutPath = m_cfg.readEntry("Krita/Ocio/OcioLutPath", QString()); cfg.inputColorSpace = m_cfg.readEntry("Krita/Ocio/InputColorSpace", QString()); cfg.displayDevice = m_cfg.readEntry("Krita/Ocio/DisplayDevice", QString()); cfg.displayView = m_cfg.readEntry("Krita/Ocio/DisplayView", QString()); cfg.look = m_cfg.readEntry("Krita/Ocio/DisplayLook", QString()); } return cfg; } void KisConfig::setOcioConfiguration(const KisOcioConfiguration &cfg) { m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) cfg.mode); m_cfg.writeEntry("Krita/Ocio/OcioConfigPath", cfg.configurationPath); m_cfg.writeEntry("Krita/Ocio/OcioLutPath", cfg.lutPath); m_cfg.writeEntry("Krita/Ocio/InputColorSpace", cfg.inputColorSpace); m_cfg.writeEntry("Krita/Ocio/DisplayDevice", cfg.displayDevice); m_cfg.writeEntry("Krita/Ocio/DisplayView", cfg.displayView); m_cfg.writeEntry("Krita/Ocio/DisplayLook", cfg.look); } KisConfig::OcioColorManagementMode KisConfig::ocioColorManagementMode(bool defaultValue) const { // FIXME: this option duplicates ocioConfiguration(), please deprecate it return (OcioColorManagementMode)(defaultValue ? INTERNAL : m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", (int) INTERNAL)); } void KisConfig::setOcioColorManagementMode(OcioColorManagementMode mode) const { // FIXME: this option duplicates ocioConfiguration(), please deprecate it m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) mode); } int KisConfig::ocioLutEdgeSize(bool defaultValue) const { return (defaultValue ? 64 : m_cfg.readEntry("Krita/Ocio/LutEdgeSize", 64)); } void KisConfig::setOcioLutEdgeSize(int value) { m_cfg.writeEntry("Krita/Ocio/LutEdgeSize", value); } bool KisConfig::ocioLockColorVisualRepresentation(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/OcioLockColorVisualRepresentation", false)); } void KisConfig::setOcioLockColorVisualRepresentation(bool value) { m_cfg.writeEntry("Krita/Ocio/OcioLockColorVisualRepresentation", value); } QString KisConfig::defaultPalette(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("defaultPalette", "Default")); } void KisConfig::setDefaultPalette(const QString& name) const { m_cfg.writeEntry("defaultPalette", name); } QString KisConfig::toolbarSlider(int sliderNumber, bool defaultValue) const { QString def = "flow"; if (sliderNumber == 1) { def = "opacity"; } if (sliderNumber == 2) { def = "size"; } return (defaultValue ? def : m_cfg.readEntry(QString("toolbarslider_%1").arg(sliderNumber), def)); } void KisConfig::setToolbarSlider(int sliderNumber, const QString &slider) { m_cfg.writeEntry(QString("toolbarslider_%1").arg(sliderNumber), slider); } int KisConfig::layerThumbnailSize(bool defaultValue) const { return (defaultValue ? 20 : m_cfg.readEntry("layerThumbnailSize", 20)); } void KisConfig::setLayerThumbnailSize(int size) { m_cfg.writeEntry("layerThumbnailSize", size); } bool KisConfig::sliderLabels(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("sliderLabels", true)); } void KisConfig::setSliderLabels(bool enabled) { m_cfg.writeEntry("sliderLabels", enabled); } QString KisConfig::currentInputProfile(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("currentInputProfile", QString())); } void KisConfig::setCurrentInputProfile(const QString& name) { m_cfg.writeEntry("currentInputProfile", name); } bool KisConfig::useSystemMonitorProfile(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ColorManagement/UseSystemMonitorProfile", false)); } void KisConfig::setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const { m_cfg.writeEntry("ColorManagement/UseSystemMonitorProfile", _useSystemMonitorProfile); } bool KisConfig::presetStripVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("presetStripVisible", true)); } void KisConfig::setPresetStripVisible(bool visible) { m_cfg.writeEntry("presetStripVisible", visible); } bool KisConfig::scratchpadVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("scratchpadVisible", true)); } void KisConfig::setScratchpadVisible(bool visible) { m_cfg.writeEntry("scratchpadVisible", visible); } bool KisConfig::showSingleChannelAsColor(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showSingleChannelAsColor", false)); } void KisConfig::setShowSingleChannelAsColor(bool asColor) { m_cfg.writeEntry("showSingleChannelAsColor", asColor); } bool KisConfig::hidePopups(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("hidePopups", false)); } void KisConfig::setHidePopups(bool hidepopups) { m_cfg.writeEntry("hidePopups", hidepopups); } int KisConfig::numDefaultLayers(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("NumberOfLayersForNewImage", 2)); } void KisConfig::setNumDefaultLayers(int num) { m_cfg.writeEntry("NumberOfLayersForNewImage", num); } quint8 KisConfig::defaultBackgroundOpacity(bool defaultValue) const { return (defaultValue ? (int)OPACITY_OPAQUE_U8 : m_cfg.readEntry("BackgroundOpacityForNewImage", (int)OPACITY_OPAQUE_U8)); } void KisConfig::setDefaultBackgroundOpacity(quint8 value) { m_cfg.writeEntry("BackgroundOpacityForNewImage", (int)value); } QColor KisConfig::defaultBackgroundColor(bool defaultValue) const { return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("BackgroundColorForNewImage", QColor(Qt::white))); } void KisConfig::setDefaultBackgroundColor(const QColor &value) { m_cfg.writeEntry("BackgroundColorForNewImage", value); } KisConfig::BackgroundStyle KisConfig::defaultBackgroundStyle(bool defaultValue) const { return (KisConfig::BackgroundStyle)(defaultValue ? RASTER_LAYER : m_cfg.readEntry("BackgroundStyleForNewImage", (int)RASTER_LAYER)); } void KisConfig::setDefaultBackgroundStyle(KisConfig::BackgroundStyle value) { m_cfg.writeEntry("BackgroundStyleForNewImage", (int)value); } int KisConfig::lineSmoothingType(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("LineSmoothingType", 1)); } void KisConfig::setLineSmoothingType(int value) { m_cfg.writeEntry("LineSmoothingType", value); } qreal KisConfig::lineSmoothingDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDistance", 50.0)); } void KisConfig::setLineSmoothingDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDistance", value); } qreal KisConfig::lineSmoothingTailAggressiveness(bool defaultValue) const { return (defaultValue ? 0.15 : m_cfg.readEntry("LineSmoothingTailAggressiveness", 0.15)); } void KisConfig::setLineSmoothingTailAggressiveness(qreal value) { m_cfg.writeEntry("LineSmoothingTailAggressiveness", value); } bool KisConfig::lineSmoothingSmoothPressure(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("LineSmoothingSmoothPressure", false)); } void KisConfig::setLineSmoothingSmoothPressure(bool value) { m_cfg.writeEntry("LineSmoothingSmoothPressure", value); } bool KisConfig::lineSmoothingScalableDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingScalableDistance", true)); } void KisConfig::setLineSmoothingScalableDistance(bool value) { m_cfg.writeEntry("LineSmoothingScalableDistance", value); } qreal KisConfig::lineSmoothingDelayDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDelayDistance", 50.0)); } void KisConfig::setLineSmoothingDelayDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDelayDistance", value); } bool KisConfig::lineSmoothingUseDelayDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingUseDelayDistance", true)); } void KisConfig::setLineSmoothingUseDelayDistance(bool value) { m_cfg.writeEntry("LineSmoothingUseDelayDistance", value); } bool KisConfig::lineSmoothingFinishStabilizedCurve(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingFinishStabilizedCurve", true)); } void KisConfig::setLineSmoothingFinishStabilizedCurve(bool value) { m_cfg.writeEntry("LineSmoothingFinishStabilizedCurve", value); } bool KisConfig::lineSmoothingStabilizeSensors(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingStabilizeSensors", true)); } void KisConfig::setLineSmoothingStabilizeSensors(bool value) { m_cfg.writeEntry("LineSmoothingStabilizeSensors", value); } int KisConfig::tabletEventsDelay(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("tabletEventsDelay", 10)); } void KisConfig::setTabletEventsDelay(int value) { m_cfg.writeEntry("tabletEventsDelay", value); } bool KisConfig::trackTabletEventLatency(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("trackTabletEventLatency", false)); } void KisConfig::setTrackTabletEventLatency(bool value) { m_cfg.writeEntry("trackTabletEventLatency", value); } bool KisConfig::testingAcceptCompressedTabletEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingAcceptCompressedTabletEvents", false)); } void KisConfig::setTestingAcceptCompressedTabletEvents(bool value) { m_cfg.writeEntry("testingAcceptCompressedTabletEvents", value); } bool KisConfig::shouldEatDriverShortcuts(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("shouldEatDriverShortcuts", false)); } bool KisConfig::testingCompressBrushEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingCompressBrushEvents", false)); } void KisConfig::setTestingCompressBrushEvents(bool value) { m_cfg.writeEntry("testingCompressBrushEvents", value); } int KisConfig::workaroundX11SmoothPressureSteps(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("workaroundX11SmoothPressureSteps", 0)); } bool KisConfig::showCanvasMessages(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showOnCanvasMessages", true)); } void KisConfig::setShowCanvasMessages(bool show) { m_cfg.writeEntry("showOnCanvasMessages", show); } bool KisConfig::compressKra(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("compressLayersInKra", false)); } void KisConfig::setCompressKra(bool compress) { m_cfg.writeEntry("compressLayersInKra", compress); } bool KisConfig::toolOptionsInDocker(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ToolOptionsInDocker", true)); } void KisConfig::setToolOptionsInDocker(bool inDocker) { m_cfg.writeEntry("ToolOptionsInDocker", inDocker); } bool KisConfig::kineticScrollingEnabled(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("KineticScrollingEnabled", true)); } void KisConfig::setKineticScrollingEnabled(bool value) { m_cfg.writeEntry("KineticScrollingEnabled", value); } int KisConfig::kineticScrollingGesture(bool defaultValue) const { - return (defaultValue ? 2 : m_cfg.readEntry("KineticScrollingGesture", 2)); +#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::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_layer_manager.cc b/libs/ui/kis_layer_manager.cc index 4506ced736..da4015598a 100644 --- a/libs/ui/kis_layer_manager.cc +++ b/libs/ui/kis_layer_manager.cc @@ -1,1002 +1,995 @@ /* * Copyright (C) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_cursor.h" #include "dialogs/kis_dlg_adj_layer_props.h" #include "dialogs/kis_dlg_adjustment_layer.h" #include "dialogs/kis_dlg_layer_properties.h" #include "dialogs/kis_dlg_generator_layer.h" #include "dialogs/kis_dlg_file_layer.h" #include "dialogs/kis_dlg_layer_style.h" #include "dialogs/KisDlgChangeCloneSource.h" #include "kis_filter_manager.h" #include "kis_node_visitor.h" #include "kis_paint_layer.h" #include "commands/kis_image_commands.h" #include "commands/kis_node_commands.h" #include "kis_change_file_layer_command.h" #include "kis_canvas_resource_provider.h" #include "kis_selection_manager.h" #include "kis_statusbar.h" #include "KisViewManager.h" #include "kis_zoom_manager.h" #include "canvas/kis_canvas2.h" #include "widgets/kis_meta_data_merge_strategy_chooser_widget.h" #include "widgets/kis_wdg_generator.h" #include "kis_progress_widget.h" #include "kis_node_commands_adapter.h" #include "kis_node_manager.h" #include "kis_action.h" #include "kis_action_manager.h" #include "kis_raster_keyframe_channel.h" #include "kis_signal_compressor_with_param.h" #include "kis_abstract_projection_plane.h" #include "commands_new/kis_set_layer_style_command.h" #include "kis_post_execution_undo_adapter.h" #include "kis_selection_mask.h" #include "kis_layer_utils.h" #include "lazybrush/kis_colorize_mask.h" #include "kis_processing_applicator.h" #include "KisSaveGroupVisitor.h" KisLayerManager::KisLayerManager(KisViewManager * view) : m_view(view) - , m_imageView(0) - , m_imageFlatten(0) - , m_imageMergeLayer(0) - , m_groupLayersSave(0) - , m_imageResizeToLayer(0) - , m_flattenLayer(0) - , m_rasterizeLayer(0) , m_commandsAdapter(new KisNodeCommandsAdapter(m_view)) - , m_layerStyle(0) + { } KisLayerManager::~KisLayerManager() { delete m_commandsAdapter; } void KisLayerManager::setView(QPointerview) { m_imageView = view; } KisLayerSP KisLayerManager::activeLayer() { if (m_imageView) { return m_imageView->currentLayer(); } return 0; } KisPaintDeviceSP KisLayerManager::activeDevice() { if (activeLayer()) { return activeLayer()->paintDevice(); } return 0; } void KisLayerManager::activateLayer(KisLayerSP layer) { if (m_imageView) { layersUpdated(); if (layer) { m_view->canvasResourceProvider()->slotNodeActivated(layer.data()); } } } void KisLayerManager::setup(KisActionManager* actionManager) { m_imageFlatten = actionManager->createAction("flatten_image"); connect(m_imageFlatten, SIGNAL(triggered()), this, SLOT(flattenImage())); m_imageMergeLayer = actionManager->createAction("merge_layer"); connect(m_imageMergeLayer, SIGNAL(triggered()), this, SLOT(mergeLayer())); m_flattenLayer = actionManager->createAction("flatten_layer"); connect(m_flattenLayer, SIGNAL(triggered()), this, SLOT(flattenLayer())); m_rasterizeLayer = actionManager->createAction("rasterize_layer"); connect(m_rasterizeLayer, SIGNAL(triggered()), this, SLOT(rasterizeLayer())); m_groupLayersSave = actionManager->createAction("save_groups_as_images"); connect(m_groupLayersSave, SIGNAL(triggered()), this, SLOT(saveGroupLayers())); m_convertGroupAnimated = actionManager->createAction("convert_group_to_animated"); connect(m_convertGroupAnimated, SIGNAL(triggered()), this, SLOT(convertGroupToAnimated())); m_imageResizeToLayer = actionManager->createAction("resizeimagetolayer"); connect(m_imageResizeToLayer, SIGNAL(triggered()), this, SLOT(imageResizeToActiveLayer())); KisAction *action = actionManager->createAction("trim_to_image"); connect(action, SIGNAL(triggered()), this, SLOT(trimToImage())); m_layerStyle = actionManager->createAction("layer_style"); connect(m_layerStyle, SIGNAL(triggered()), this, SLOT(layerStyle())); } void KisLayerManager::updateGUI() { KisImageSP image = m_view->image(); KisLayerSP layer = activeLayer(); const bool isGroupLayer = layer && layer->inherits("KisGroupLayer"); m_imageMergeLayer->setText( isGroupLayer ? i18nc("@action:inmenu", "Merge Group") : i18nc("@action:inmenu", "Merge with Layer Below")); m_flattenLayer->setVisible(!isGroupLayer); if (m_view->statusBar()) m_view->statusBar()->setProfile(image); } void KisLayerManager::imageResizeToActiveLayer() { KisLayerSP layer; KisImageWSP image = m_view->image(); if (image && (layer = activeLayer())) { QRect cropRect = layer->projection()->nonDefaultPixelArea(); if (!cropRect.isEmpty()) { image->cropImage(cropRect); } else { m_view->showFloatingMessage( i18nc("floating message in layer manager", "Layer is empty "), QIcon(), 2000, KisFloatingMessage::Low); } } } void KisLayerManager::trimToImage() { KisImageWSP image = m_view->image(); if (image) { image->cropImage(image->bounds()); } } void KisLayerManager::layerProperties() { if (!m_view) return; if (!m_view->document()) return; KisLayerSP layer = activeLayer(); if (!layer) return; QList selectedNodes = m_view->nodeManager()->selectedNodes(); const bool multipleLayersSelected = selectedNodes.size() > 1; KisAdjustmentLayerSP adjustmentLayer = KisAdjustmentLayerSP(dynamic_cast(layer.data())); KisGeneratorLayerSP generatorLayer = KisGeneratorLayerSP(dynamic_cast(layer.data())); KisFileLayerSP fileLayer = KisFileLayerSP(dynamic_cast(layer.data())); if (adjustmentLayer && !multipleLayersSelected) { KisPaintDeviceSP dev = adjustmentLayer->projection(); KisDlgAdjLayerProps dlg(adjustmentLayer, adjustmentLayer.data(), dev, m_view, adjustmentLayer->filter().data(), adjustmentLayer->name(), i18n("Filter Layer Properties"), m_view->mainWindow(), "dlgadjlayerprops"); dlg.resize(dlg.minimumSizeHint()); KisFilterConfigurationSP configBefore(adjustmentLayer->filter()); KIS_ASSERT_RECOVER_RETURN(configBefore); QString xmlBefore = configBefore->toXML(); if (dlg.exec() == QDialog::Accepted) { adjustmentLayer->setName(dlg.layerName()); KisFilterConfigurationSP configAfter(dlg.filterConfiguration()); Q_ASSERT(configAfter); QString xmlAfter = configAfter->toXML(); if(xmlBefore != xmlAfter) { KisChangeFilterCmd *cmd = new KisChangeFilterCmd(adjustmentLayer, configBefore->name(), xmlBefore, configAfter->name(), xmlAfter, false); // FIXME: check whether is needed cmd->redo(); m_view->undoAdapter()->addCommand(cmd); m_view->document()->setModified(true); } } else { KisFilterConfigurationSP configAfter(dlg.filterConfiguration()); Q_ASSERT(configAfter); QString xmlAfter = configAfter->toXML(); if(xmlBefore != xmlAfter) { adjustmentLayer->setFilter(KisFilterRegistry::instance()->cloneConfiguration(configBefore.data())); adjustmentLayer->setDirty(); } } } else if (generatorLayer && !multipleLayersSelected) { KisFilterConfigurationSP configBefore(generatorLayer->filter()); Q_ASSERT(configBefore); QString xmlBefore = configBefore->toXML(); KisDlgGeneratorLayer *dlg = new KisDlgGeneratorLayer(generatorLayer->name(), m_view, m_view->mainWindow(), generatorLayer, configBefore); dlg->setCaption(i18n("Fill Layer Properties")); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setConfiguration(configBefore.data()); dlg->resize(dlg->minimumSizeHint()); Qt::WindowFlags flags = dlg->windowFlags(); dlg->setWindowFlags(flags | Qt::Tool | Qt::Dialog); dlg->show(); } else if (fileLayer && !multipleLayersSelected){ QString basePath = QFileInfo(m_view->document()->url().toLocalFile()).absolutePath(); QString fileNameOld = fileLayer->fileName(); KisFileLayer::ScalingMethod scalingMethodOld = fileLayer->scalingMethod(); KisDlgFileLayer dlg(basePath, fileLayer->name(), m_view->mainWindow()); dlg.setCaption(i18n("File Layer Properties")); dlg.setFileName(fileNameOld); dlg.setScalingMethod(scalingMethodOld); if (dlg.exec() == QDialog::Accepted) { const QString fileNameNew = dlg.fileName(); KisFileLayer::ScalingMethod scalingMethodNew = dlg.scaleToImageResolution(); if(fileNameNew.isEmpty()){ QMessageBox::critical(m_view->mainWindow(), i18nc("@title:window", "Krita"), i18n("No file name specified")); return; } fileLayer->setName(dlg.layerName()); if (fileNameOld!= fileNameNew || scalingMethodOld != scalingMethodNew) { KisChangeFileLayerCmd *cmd = new KisChangeFileLayerCmd(fileLayer, basePath, fileNameOld, scalingMethodOld, basePath, fileNameNew, scalingMethodNew); m_view->undoAdapter()->addCommand(cmd); } } } else { // If layer == normal painting layer, vector layer, or group layer QList selectedNodes = m_view->nodeManager()->selectedNodes(); KisDlgLayerProperties *dialog = new KisDlgLayerProperties(selectedNodes, m_view); dialog->resize(dialog->minimumSizeHint()); dialog->setAttribute(Qt::WA_DeleteOnClose); Qt::WindowFlags flags = dialog->windowFlags(); dialog->setWindowFlags(flags | Qt::Tool | Qt::Dialog); dialog->show(); } } void KisLayerManager::changeCloneSource() { QList selectedNodes = m_view->nodeManager()->selectedNodes(); if (selectedNodes.isEmpty()) { return; } QList cloneLayers; KisNodeSP node; Q_FOREACH (node, selectedNodes) { KisCloneLayerSP cloneLayer(qobject_cast(node.data())); if (cloneLayer) { cloneLayers << cloneLayer; } } if (cloneLayers.isEmpty()) { return; } KisDlgChangeCloneSource *dialog = new KisDlgChangeCloneSource(cloneLayers, m_view); dialog->setCaption(i18n("Change Clone Layer")); dialog->resize(dialog->minimumSizeHint()); dialog->setAttribute(Qt::WA_DeleteOnClose); Qt::WindowFlags flags = dialog->windowFlags(); dialog->setWindowFlags(flags | Qt::Tool | Qt::Dialog); dialog->show(); } void KisLayerManager::convertNodeToPaintLayer(KisNodeSP source) { KisImageWSP image = m_view->image(); if (!image) return; KisLayer *srcLayer = qobject_cast(source.data()); if (srcLayer && (srcLayer->inherits("KisGroupLayer") || srcLayer->layerStyle() || srcLayer->childCount() > 0)) { image->flattenLayer(srcLayer); return; } KisPaintDeviceSP srcDevice = source->paintDevice() ? source->projection() : source->original(); bool putBehind = false; QString newCompositeOp = source->compositeOpId(); KisColorizeMask *colorizeMask = dynamic_cast(source.data()); if (colorizeMask) { srcDevice = colorizeMask->coloringProjection(); putBehind = colorizeMask->compositeOpId() == COMPOSITE_BEHIND; if (putBehind) { newCompositeOp = COMPOSITE_OVER; } } if (!srcDevice) return; KisPaintDeviceSP clone; if (*srcDevice->colorSpace() != *srcDevice->compositionSourceColorSpace()) { clone = new KisPaintDevice(srcDevice->compositionSourceColorSpace()); QRect rc(srcDevice->extent()); KisPainter::copyAreaOptimized(rc.topLeft(), srcDevice, clone, rc); } else { clone = new KisPaintDevice(*srcDevice); } KisLayerSP layer = new KisPaintLayer(image, source->name(), source->opacity(), clone); layer->setCompositeOpId(newCompositeOp); KisNodeSP parent = source->parent(); KisNodeSP above = source->prevSibling(); while (parent && !parent->allowAsChild(layer)) { above = above ? above->parent() : source->parent(); parent = above ? above->parent() : 0; } if (putBehind && above == source->parent()) { above = above->prevSibling(); } m_commandsAdapter->beginMacro(kundo2_i18n("Convert to a Paint Layer")); m_commandsAdapter->removeNode(source); m_commandsAdapter->addNode(layer, parent, above); m_commandsAdapter->endMacro(); } void KisLayerManager::convertGroupToAnimated() { KisGroupLayerSP group = dynamic_cast(activeLayer().data()); if (group.isNull()) return; KisPaintLayerSP animatedLayer = new KisPaintLayer(m_view->image(), group->name(), OPACITY_OPAQUE_U8); animatedLayer->enableAnimation(); KisRasterKeyframeChannel *contentChannel = dynamic_cast( animatedLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true)); KIS_ASSERT_RECOVER_RETURN(contentChannel); KisNodeSP child = group->firstChild(); int time = 0; while (child) { contentChannel->importFrame(time, child->projection(), NULL); time++; child = child->nextSibling(); } m_commandsAdapter->beginMacro(kundo2_i18n("Convert to an animated layer")); m_commandsAdapter->addNode(animatedLayer, group->parent(), group); m_commandsAdapter->removeNode(group); m_commandsAdapter->endMacro(); } void KisLayerManager::convertLayerToFileLayer(KisNodeSP source) { KisImageSP image = m_view->image(); if (!image) return; QStringList listMimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); KoDialog dlg; QWidget *page = new QWidget(&dlg); dlg.setMainWidget(page); QBoxLayout *layout = new QVBoxLayout(page); dlg.setWindowTitle(i18n("Save layers to...")); QLabel *lbl = new QLabel(i18n("Choose the location where the layer will be saved to. The new file layer will then reference this location.")); lbl->setWordWrap(true); layout->addWidget(lbl); KisFileNameRequester *urlRequester = new KisFileNameRequester(page); urlRequester->setMode(KoFileDialog::SaveFile); urlRequester->setMimeTypeFilters(listMimeFilter); urlRequester->setFileName(m_view->document()->url().toLocalFile()); if (m_view->document()->url().isLocalFile()) { QFileInfo location = QFileInfo(m_view->document()->url().toLocalFile()).completeBaseName(); location.setFile(location.dir(), location.completeBaseName() + "_" + source->name() + ".png"); urlRequester->setFileName(location.absoluteFilePath()); } else { const QFileInfo location = QFileInfo(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); const QString proposedFileName = QDir(location.absoluteFilePath()).absoluteFilePath(source->name() + ".png"); urlRequester->setFileName(proposedFileName); } layout->addWidget(urlRequester); if (!dlg.exec()) return; QString path = urlRequester->fileName(); if (path.isEmpty()) return; QFileInfo f(path); QString mimeType= KisMimeDatabase::mimeTypeForFile(f.fileName()); if (mimeType.isEmpty()) { mimeType = "image/png"; } QScopedPointer doc(KisPart::instance()->createDocument()); QRect bounds = source->exactBounds(); if (bounds.isEmpty()) { bounds = image->bounds(); } KisImageSP dst = new KisImage(doc->createUndoStore(), image->width(), image->height(), image->projection()->compositionSourceColorSpace(), source->name()); dst->setResolution(image->xRes(), image->yRes()); doc->setFileBatchMode(false); doc->setCurrentImage(dst); KisNodeSP node = source->clone(); dst->addNode(node); dst->initialRefreshGraph(); dst->cropImage(bounds); dst->waitForDone(); bool r = doc->exportDocumentSync(QUrl::fromLocalFile(path), mimeType.toLatin1()); if (!r) { qWarning() << "Converting layer to file layer. path:"<< path << "gave errors" << doc->errorMessage(); } else { QString basePath = QFileInfo(m_view->document()->url().toLocalFile()).absolutePath(); QString relativePath = QDir(basePath).relativeFilePath(path); KisFileLayer *fileLayer = new KisFileLayer(image, basePath, relativePath, KisFileLayer::None, source->name(), OPACITY_OPAQUE_U8); fileLayer->setX(bounds.x()); fileLayer->setY(bounds.y()); KisNodeSP dstParent = source->parent(); KisNodeSP dstAboveThis = source->prevSibling(); m_commandsAdapter->beginMacro(kundo2_i18n("Convert to a file layer")); m_commandsAdapter->removeNode(source); m_commandsAdapter->addNode(fileLayer, dstParent, dstAboveThis); m_commandsAdapter->endMacro(); } doc->closeUrl(false); } void KisLayerManager::adjustLayerPosition(KisNodeSP node, KisNodeSP activeNode, KisNodeSP &parent, KisNodeSP &above) { Q_ASSERT(activeNode); parent = activeNode; above = parent->lastChild(); if (parent->inherits("KisGroupLayer") && parent->collapsed()) { above = parent; parent = parent->parent(); return; } while (parent && (!parent->allowAsChild(node) || parent->userLocked())) { above = parent; parent = parent->parent(); } if (!parent) { warnKrita << "KisLayerManager::adjustLayerPosition:" << "No node accepted newly created node"; parent = m_view->image()->root(); above = parent->lastChild(); } } void KisLayerManager::addLayerCommon(KisNodeSP activeNode, KisNodeSP layer, bool updateImage, KisProcessingApplicator *applicator) { KisNodeSP parent; KisNodeSP above; adjustLayerPosition(layer, activeNode, parent, above); KisGroupLayer *group = dynamic_cast(parent.data()); const bool parentForceUpdate = group && !group->projectionIsValid(); updateImage |= parentForceUpdate; m_commandsAdapter->addNodeAsync(layer, parent, above, updateImage, updateImage, applicator); } KisLayerSP KisLayerManager::addPaintLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); KisLayerSP layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, image->colorSpace()); addLayerCommon(activeNode, layer, false, 0); return layer; } KisNodeSP KisLayerManager::addGroupLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); KisGroupLayerSP group = new KisGroupLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8); addLayerCommon(activeNode, group, false, 0); return group; } KisNodeSP KisLayerManager::addCloneLayer(KisNodeList nodes) { KisImageWSP image = m_view->image(); KisNodeList filteredNodes = KisLayerUtils::sortAndFilterMergableInternalNodes(nodes, false); if (filteredNodes.isEmpty()) return KisNodeSP(); KisNodeSP newAbove = filteredNodes.last(); KisNodeSP node, lastClonedNode; Q_FOREACH (node, filteredNodes) { lastClonedNode = new KisCloneLayer(qobject_cast(node.data()), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8); addLayerCommon(newAbove, lastClonedNode, true, 0 ); } return lastClonedNode; } KisNodeSP KisLayerManager::addShapeLayer(KisNodeSP activeNode) { if (!m_view) return 0; if (!m_view->document()) return 0; KisImageWSP image = m_view->image(); KisShapeLayerSP layer = new KisShapeLayer(m_view->document()->shapeController(), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8); addLayerCommon(activeNode, layer, false, 0); return layer; } KisNodeSP KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); KisSelectionSP selection = m_view->selection(); KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Add Layer")); KisAdjustmentLayerSP adjl = addAdjustmentLayer(activeNode, QString(), 0, selection, &applicator); KisPaintDeviceSP previewDevice = new KisPaintDevice(*adjl->original()); KisDlgAdjustmentLayer dlg(adjl, adjl.data(), previewDevice, image->nextLayerName(), i18n("New Filter Layer"), m_view, qApp->activeWindow()); dlg.resize(dlg.minimumSizeHint()); // ensure that the device may be free'd by the dialog // when it is not needed anymore previewDevice = 0; if (dlg.exec() != QDialog::Accepted || adjl->filter().isNull()) { // XXX: add messagebox warning if there's no filter set! applicator.cancel(); } else { adjl->setName(dlg.layerName()); applicator.end(); } return adjl; } KisAdjustmentLayerSP KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode, const QString & name, KisFilterConfigurationSP filter, KisSelectionSP selection, KisProcessingApplicator *applicator) { KisImageWSP image = m_view->image(); KisAdjustmentLayerSP layer = new KisAdjustmentLayer(image, name, filter, selection); addLayerCommon(activeNode, layer, true, applicator); return layer; } KisNodeSP KisLayerManager::addGeneratorLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); QColor currentForeground = m_view->canvasResourceProvider()->fgColor().toQColor(); KisDlgGeneratorLayer dlg(image->nextLayerName(), m_view, m_view->mainWindow(), 0, 0); KisFilterConfigurationSP defaultConfig = dlg.configuration(); defaultConfig->setProperty("color", currentForeground); dlg.setConfiguration(defaultConfig); dlg.resize(dlg.minimumSizeHint()); if (dlg.exec() == QDialog::Accepted) { KisSelectionSP selection = m_view->selection(); KisFilterConfigurationSP generator = dlg.configuration(); QString name = dlg.layerName(); KisNodeSP node = new KisGeneratorLayer(image, name, generator, selection); addLayerCommon(activeNode, node, true, 0); return node; } return 0; } void KisLayerManager::flattenImage() { KisImageSP image = m_view->image(); if (!m_view->blockUntilOperationsFinished(image)) return; if (image) { bool doIt = true; if (image->nHiddenLayers() > 0) { int answer = QMessageBox::warning(m_view->mainWindow(), i18nc("@title:window", "Flatten Image"), i18n("The image contains hidden layers that will be lost. Do you want to flatten the image?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (answer != QMessageBox::Yes) { doIt = false; } } if (doIt) { image->flatten(m_view->activeNode()); } } } inline bool isSelectionMask(KisNodeSP node) { return dynamic_cast(node.data()); } bool tryMergeSelectionMasks(KisNodeSP currentNode, KisImageSP image) { bool result = false; KisNodeSP prevNode = currentNode->prevSibling(); if (isSelectionMask(currentNode) && prevNode && isSelectionMask(prevNode)) { QList mergedNodes; mergedNodes.append(currentNode); mergedNodes.append(prevNode); image->mergeMultipleLayers(mergedNodes, currentNode); result = true; } return result; } bool tryFlattenGroupLayer(KisNodeSP currentNode, KisImageSP image) { bool result = false; if (currentNode->inherits("KisGroupLayer")) { KisGroupLayer *layer = qobject_cast(currentNode.data()); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(layer, false); image->flattenLayer(layer); result = true; } return result; } void KisLayerManager::mergeLayer() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(image)) return; QList selectedNodes = m_view->nodeManager()->selectedNodes(); if (selectedNodes.size() > 1) { image->mergeMultipleLayers(selectedNodes, m_view->activeNode()); } else if (tryMergeSelectionMasks(m_view->activeNode(), image)) { // already done! } else if (tryFlattenGroupLayer(m_view->activeNode(), image)) { // already done! } else { if (!layer->prevSibling()) return; KisLayer *prevLayer = qobject_cast(layer->prevSibling().data()); if (!prevLayer) return; if (prevLayer->userLocked()) { m_view->showFloatingMessage( i18nc("floating message in layer manager", "Layer is locked "), QIcon(), 2000, KisFloatingMessage::Low); } else if (layer->metaData()->isEmpty() && prevLayer->metaData()->isEmpty()) { image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); } else { const KisMetaData::MergeStrategy* strategy = KisMetaDataMergeStrategyChooserWidget::showDialog(m_view->mainWindow()); if (!strategy) return; image->mergeDown(layer, strategy); } } m_view->updateGUI(); } void KisLayerManager::flattenLayer() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(image)) return; convertNodeToPaintLayer(layer); m_view->updateGUI(); } void KisLayerManager::rasterizeLayer() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(image)) return; KisPaintLayerSP paintLayer = new KisPaintLayer(image, layer->name(), layer->opacity()); KisPainter gc(paintLayer->paintDevice()); QRect rc = layer->projection()->exactBounds(); gc.bitBlt(rc.topLeft(), layer->projection(), rc); m_commandsAdapter->beginMacro(kundo2_i18n("Rasterize Layer")); m_commandsAdapter->addNode(paintLayer.data(), layer->parent().data(), layer.data()); int childCount = layer->childCount(); for (int i = 0; i < childCount; i++) { m_commandsAdapter->moveNode(layer->firstChild(), paintLayer, paintLayer->lastChild()); } m_commandsAdapter->removeNode(layer); m_commandsAdapter->endMacro(); updateGUI(); } void KisLayerManager::layersUpdated() { KisLayerSP layer = activeLayer(); if (!layer) return; m_view->updateGUI(); } void KisLayerManager::saveGroupLayers() { QStringList listMimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); KoDialog dlg; QWidget *page = new QWidget(&dlg); dlg.setMainWidget(page); QBoxLayout *layout = new QVBoxLayout(page); KisFileNameRequester *urlRequester = new KisFileNameRequester(page); urlRequester->setMode(KoFileDialog::SaveFile); if (m_view->document()->url().isLocalFile()) { urlRequester->setStartDir(QFileInfo(m_view->document()->url().toLocalFile()).absolutePath()); } urlRequester->setMimeTypeFilters(listMimeFilter); urlRequester->setFileName(m_view->document()->url().toLocalFile()); layout->addWidget(urlRequester); QCheckBox *chkInvisible = new QCheckBox(i18n("Convert Invisible Groups"), page); chkInvisible->setChecked(false); layout->addWidget(chkInvisible); QCheckBox *chkDepth = new QCheckBox(i18n("Export Only Toplevel Groups"), page); chkDepth->setChecked(true); layout->addWidget(chkDepth); if (!dlg.exec()) return; QString path = urlRequester->fileName(); if (path.isEmpty()) return; QFileInfo f(path); QString mimeType= KisMimeDatabase::mimeTypeForFile(f.fileName(), false); if (mimeType.isEmpty()) { mimeType = "image/png"; } QString extension = KisMimeDatabase::suffixesForMimeType(mimeType).first(); QString basename = f.completeBaseName(); KisImageSP image = m_view->image(); if (!image) return; KisSaveGroupVisitor v(image, chkInvisible->isChecked(), chkDepth->isChecked(), f.absolutePath(), basename, extension, mimeType); image->rootLayer()->accept(v); } bool KisLayerManager::activeLayerHasSelection() { return (activeLayer()->selection() != 0); } KisNodeSP KisLayerManager::addFileLayer(KisNodeSP activeNode) { QString basePath; QUrl url = m_view->document()->url(); if (url.isLocalFile()) { basePath = QFileInfo(url.toLocalFile()).absolutePath(); } KisImageWSP image = m_view->image(); KisDlgFileLayer dlg(basePath, image->nextLayerName(), m_view->mainWindow()); dlg.resize(dlg.minimumSizeHint()); if (dlg.exec() == QDialog::Accepted) { QString name = dlg.layerName(); QString fileName = dlg.fileName(); if(fileName.isEmpty()){ QMessageBox::critical(m_view->mainWindow(), i18nc("@title:window", "Krita"), i18n("No file name specified")); return 0; } KisFileLayer::ScalingMethod scalingMethod = dlg.scaleToImageResolution(); KisNodeSP node = new KisFileLayer(image, basePath, fileName, scalingMethod, name, OPACITY_OPAQUE_U8); addLayerCommon(activeNode, node, true, 0); return node; } return 0; } void updateLayerStyles(KisLayerSP layer, KisDlgLayerStyle *dlg) { KisSetLayerStyleCommand::updateLayerStyle(layer, dlg->style()->clone()); } void KisLayerManager::layerStyle() { KisImageWSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(image)) return; KisPSDLayerStyleSP oldStyle; if (layer->layerStyle()) { oldStyle = layer->layerStyle()->clone(); } else { oldStyle = toQShared(new KisPSDLayerStyle()); } KisDlgLayerStyle dlg(oldStyle->clone(), m_view->canvasResourceProvider()); std::function updateCall(std::bind(updateLayerStyles, layer, &dlg)); SignalToFunctionProxy proxy(updateCall); connect(&dlg, SIGNAL(configChanged()), &proxy, SLOT(start())); if (dlg.exec() == QDialog::Accepted) { KisPSDLayerStyleSP newStyle = dlg.style(); KUndo2CommandSP command = toQShared( new KisSetLayerStyleCommand(layer, oldStyle, newStyle)); image->postExecutionUndoAdapter()->addCommand(command); } } diff --git a/libs/ui/kis_layer_manager.h b/libs/ui/kis_layer_manager.h index 0e93d4eab8..0a5dce153f 100644 --- a/libs/ui/kis_layer_manager.h +++ b/libs/ui/kis_layer_manager.h @@ -1,134 +1,134 @@ /* * Copyright (C) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_LAYER_MANAGER #define KIS_LAYER_MANAGER #include #include #include #include "kis_adjustment_layer.h" #include "kis_types.h" #include "KisView.h" #include class KisViewManager; class KisNodeCommandsAdapter; class KisAction; class KisActionManager; class KisProcessingApplicator; /** * KisLayerManager takes care of the gui around working with layers: * adding, removing, editing. It also keeps track of the active layer * for this view. */ class KisLayerManager : public QObject { Q_OBJECT public: KisLayerManager(KisViewManager * view); ~KisLayerManager() override; void setView(QPointerview); Q_SIGNALS: private: friend class KisNodeManager; /** * Activate the specified layer. The layer may be 0. */ void activateLayer(KisLayerSP layer); KisLayerSP activeLayer(); KisPaintDeviceSP activeDevice(); void setup(KisActionManager *actionManager); void updateGUI(); private Q_SLOTS: void mergeLayer(); void imageResizeToActiveLayer(); void trimToImage(); void layerProperties(); void flattenImage(); void flattenLayer(); void rasterizeLayer(); void layersUpdated(); void saveGroupLayers(); bool activeLayerHasSelection(); void convertNodeToPaintLayer(KisNodeSP source); void convertGroupToAnimated(); void convertLayerToFileLayer(KisNodeSP source); KisLayerSP addPaintLayer(KisNodeSP activeNode); KisNodeSP addGroupLayer(KisNodeSP activeNode); KisNodeSP addCloneLayer(KisNodeList nodes); KisNodeSP addShapeLayer(KisNodeSP activeNode); KisNodeSP addAdjustmentLayer(KisNodeSP activeNode); KisAdjustmentLayerSP addAdjustmentLayer(KisNodeSP activeNode, const QString & name, KisFilterConfigurationSP filter, KisSelectionSP selection, KisProcessingApplicator *applicator); KisNodeSP addGeneratorLayer(KisNodeSP activeNode); KisNodeSP addFileLayer(KisNodeSP activeNode); void layerStyle(); void changeCloneSource(); private: void adjustLayerPosition(KisNodeSP node, KisNodeSP activeNode, KisNodeSP &parent, KisNodeSP &above); void addLayerCommon(KisNodeSP activeNode, KisNodeSP layer, bool updateImage = true, KisProcessingApplicator *applicator = 0); private: KisViewManager * m_view; - QPointerm_imageView; - - KisAction *m_imageFlatten; - KisAction *m_imageMergeLayer; - KisAction *m_groupLayersSave; - KisAction *m_convertGroupAnimated; - KisAction *m_imageResizeToLayer; - KisAction *m_flattenLayer; - KisAction *m_rasterizeLayer; + QPointerm_imageView {0}; + + KisAction *m_imageFlatten {0}; + KisAction *m_imageMergeLayer {0}; + KisAction *m_groupLayersSave {0}; + KisAction *m_convertGroupAnimated {0}; + KisAction *m_imageResizeToLayer {0}; + KisAction *m_flattenLayer {0}; + KisAction *m_rasterizeLayer {0}; KisNodeCommandsAdapter* m_commandsAdapter; - KisAction *m_layerStyle; + KisAction *m_layerStyle {0}; }; #endif diff --git a/libs/ui/kis_selection_manager.cc b/libs/ui/kis_selection_manager.cc index fa789f9ee7..2f3e2831a0 100644 --- a/libs/ui/kis_selection_manager.cc +++ b/libs/ui/kis_selection_manager.cc @@ -1,759 +1,742 @@ /* * 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_doc(0), - m_imageView(0), - m_adapter(new KisNodeCommandsAdapter(view)), - m_copy(0), - m_copyMerged(0), - m_cut(0), - m_paste(0), - m_pasteNew(0), - m_cutToNewLayer(0), - m_selectAll(0), - m_deselect(0), - m_clear(0), - m_reselect(0), - m_invert(0), - m_copyToNewLayer(0), - m_fillForegroundColor(0), - m_fillBackgroundColor(0), - m_fillPattern(0), - m_imageResizeToSelection(0), - m_selectionDecoration(0) + : 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(); } bool KisSelectionManager::haveShapeSelectionWithShapes() { KisSelectionSP selection = m_view->selection(); return selection && selection->hasShapeSelection(); } bool KisSelectionManager::haveRasterSelectionWithPixels() { KisSelectionSP selection = m_view->selection(); return selection && selection->hasPixelSelection() && !selection->hasShapeSelection(); } 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; } + qDebug() << "isVectorLayer" << isVectorLayer; + 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; 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/kis_selection_manager.h b/libs/ui/kis_selection_manager.h index b2f16fd5f0..733cf28327 100644 --- a/libs/ui/kis_selection_manager.h +++ b/libs/ui/kis_selection_manager.h @@ -1,179 +1,179 @@ /* * 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_MANAGER_ #define KIS_SELECTION_MANAGER_ #include #include #include #include #include "KisView.h" #include #include class KisActionManager; class KisAction; class QAction; class KisDocument; class KisViewManager; class KisClipboard; class KisNodeCommandsAdapter; class KisView; class KisSelectionFilter; class KisSelectionDecoration; /** * The selection manager is responsible selections * and the clipboard. */ class KRITAUI_EXPORT KisSelectionManager : public QObject { Q_OBJECT Q_PROPERTY(bool displaySelection READ displaySelection NOTIFY displaySelectionChanged); Q_PROPERTY(bool havePixelsSelected READ havePixelsSelected NOTIFY currentSelectionChanged); public: KisSelectionManager(KisViewManager * view); ~KisSelectionManager() override; void setup(KisActionManager* actionManager); void setView(QPointerimageView); public: /** * This function return if the selection should be displayed */ bool displaySelection(); bool showSelectionAsMask() const; public Q_SLOTS: void updateGUI(); void selectionChanged(); void clipboardDataChanged(); void cut(); void copy(); void cutSharp(); void copySharp(); void copyMerged(); void paste(); void pasteNew(); void pasteAt(); void pasteAsReference(); void cutToNewLayer(); void selectAll(); void deselect(); void invert(); void clear(); void fillForegroundColor(); void fillBackgroundColor(); void fillPattern(); void fillForegroundColorOpacity(); void fillBackgroundColorOpacity(); void fillPatternOpacity(); void reselect(); void editSelection(); void convertToVectorSelection(); void convertToRasterSelection(); void convertShapesToVectorSelection(); void convertToShape(); void copySelectionToNewLayer(); void toggleDisplaySelection(); void shapeSelectionChanged(); void imageResizeToSelection(); void paintSelectedShapes(); void slotToggleSelectionDecoration(); void slotStrokeSelection(); void selectOpaqueOnNode(KisNodeSP node, SelectionAction action); Q_SIGNALS: void currentSelectionChanged(); void signalUpdateGUI(); void displaySelectionChanged(); void strokeSelected(); public: bool havePixelsSelected(); bool havePixelsInClipboard(); bool haveShapesSelected(); bool haveShapesInClipboard(); /// Checks if the current selection is editable and has some pixels selected in the pixel selection bool haveAnySelectionWithPixels(); bool haveShapeSelectionWithShapes(); bool haveRasterSelectionWithPixels(); private: void fill(const KoColor& color, bool fillWithPattern, const QString& transactionText); void updateStatusBar(); - KisViewManager * m_view; - KisDocument * m_doc; - QPointerm_imageView; - KisClipboard * m_clipboard; - - KisNodeCommandsAdapter* m_adapter; - - KisAction *m_copy; - KisAction *m_copyMerged; - KisAction *m_cut; - KisAction *m_paste; - KisAction *m_pasteAt; - KisAction *m_pasteAsReference; - KisAction *m_pasteNew; - KisAction *m_cutToNewLayer; - KisAction *m_selectAll; - KisAction *m_deselect; - KisAction *m_clear; - KisAction *m_reselect; - KisAction *m_invert; - KisAction *m_copyToNewLayer; - KisAction *m_fillForegroundColor; - KisAction *m_fillBackgroundColor; - KisAction *m_fillPattern; - KisAction *m_fillForegroundColorOpacity; - KisAction *m_fillBackgroundColorOpacity; - KisAction *m_fillPatternOpacity; - KisAction *m_imageResizeToSelection; - KisAction *m_strokeShapes; - KisAction *m_toggleDisplaySelection; - KisAction *m_toggleSelectionOverlayMode; - KisAction *m_strokeSelected; + KisViewManager * m_view {0}; + KisDocument * m_doc {0}; + QPointerm_imageView {0}; + KisClipboard * m_clipboard {0}; + + KisNodeCommandsAdapter* m_adapter {0}; + + KisAction *m_copy {0}; + KisAction *m_copyMerged {0}; + KisAction *m_cut {0}; + KisAction *m_paste {0}; + KisAction *m_pasteAt {0}; + KisAction *m_pasteAsReference {0}; + KisAction *m_pasteNew {0}; + KisAction *m_cutToNewLayer {0}; + KisAction *m_selectAll {0}; + KisAction *m_deselect {0}; + KisAction *m_clear {0}; + KisAction *m_reselect {0}; + KisAction *m_invert {0}; + KisAction *m_copyToNewLayer {0}; + KisAction *m_fillForegroundColor {0}; + KisAction *m_fillBackgroundColor {0}; + KisAction *m_fillPattern {0}; + KisAction *m_fillForegroundColorOpacity {0}; + KisAction *m_fillBackgroundColorOpacity {0}; + KisAction *m_fillPatternOpacity {0}; + KisAction *m_imageResizeToSelection {0}; + KisAction *m_strokeShapes {0}; + KisAction *m_toggleDisplaySelection {0}; + KisAction *m_toggleSelectionOverlayMode {0}; + KisAction *m_strokeSelected {0}; QList m_pluginActions; QPointer m_selectionDecoration; }; #endif // KIS_SELECTION_MANAGER_ diff --git a/libs/ui/kis_statusbar.cc b/libs/ui/kis_statusbar.cc index d66801e553..c7c33f6595 100644 --- a/libs/ui/kis_statusbar.cc +++ b/libs/ui/kis_statusbar.cc @@ -1,414 +1,415 @@ /* This file is part of KimageShop^WKrayon^WKrita * * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_statusbar.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_memory_statistics_server.h" #include "KisView.h" #include "KisDocument.h" #include "KisViewManager.h" #include "canvas/kis_canvas2.h" #include "kis_progress_widget.h" #include "kis_zoom_manager.h" #include "KisMainWindow.h" #include "kis_config.h" #include "widgets/KisMemoryReportButton.h" enum { IMAGE_SIZE_ID, POINTER_POSITION_ID }; KisStatusBar::KisStatusBar(KisViewManager *viewManager) : m_viewManager(viewManager) , m_imageView(0) , m_statusBar(0) { } void KisStatusBar::setup() { m_selectionStatus = new QToolButton(); m_selectionStatus->setObjectName("selection status"); m_selectionStatus->setIconSize(QSize(16,16)); m_selectionStatus->setAutoRaise(true); m_selectionStatus->setEnabled(false); updateSelectionIcon(); m_statusBar = m_viewManager->mainWindow()->statusBar(); connect(m_selectionStatus, SIGNAL(clicked()), m_viewManager->selectionManager(), SLOT(slotToggleSelectionDecoration())); connect(m_viewManager->selectionManager(), SIGNAL(displaySelectionChanged()), SLOT(updateSelectionToolTip())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), this, SLOT(updateSelectionIcon())); addStatusBarItem(m_selectionStatus); m_selectionStatus->setVisible(false); - m_resetAngleButton = new QToolButton; - m_resetAngleButton->setObjectName("Reset Rotation"); - m_resetAngleButton->setCheckable(false); - m_resetAngleButton->setToolTip(i18n("Reset Rotation")); - m_resetAngleButton->setAutoRaise(true); - m_resetAngleButton->setIcon(KisIconUtils::loadIcon("rotate-canvas-left")); - addStatusBarItem(m_resetAngleButton); - - connect(m_resetAngleButton, SIGNAL(clicked()), m_viewManager, SLOT(slotResetRotation())); - m_resetAngleButton->setVisible(false); - m_statusBarStatusLabel = new KSqueezedTextLabel(); m_statusBarStatusLabel->setObjectName("statsBarStatusLabel"); m_statusBarStatusLabel->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); m_statusBarStatusLabel->setContentsMargins(5, 5, 5, 5); connect(KoToolManager::instance(), SIGNAL(changedStatusText(QString)), m_statusBarStatusLabel, SLOT(setText(QString))); addStatusBarItem(m_statusBarStatusLabel, 2); m_statusBarStatusLabel->setVisible(false); m_statusBarProfileLabel = new KSqueezedTextLabel(); m_statusBarProfileLabel->setObjectName("statsBarProfileLabel"); m_statusBarProfileLabel->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); m_statusBarProfileLabel->setContentsMargins(5, 5, 5, 5); addStatusBarItem(m_statusBarProfileLabel, 3); m_statusBarProfileLabel->setVisible(false); m_progress = new KisProgressWidget(); m_progress->setObjectName("ProgressBar"); addStatusBarItem(m_progress); m_progress->setVisible(false); connect(m_progress, SIGNAL(sigCancellationRequested()), this, SIGNAL(sigCancellationRequested())); m_progressUpdater.reset(new KisProgressUpdater(m_progress, m_progress->progressProxy())); m_progressUpdater->setAutoNestNames(true); m_memoryReportBox = new KisMemoryReportButton(); m_memoryReportBox->setObjectName("memoryReportBox"); m_memoryReportBox->setFlat(true); m_memoryReportBox->setContentsMargins(5, 5, 5, 5); m_memoryReportBox->setMinimumWidth(120); addStatusBarItem(m_memoryReportBox); m_memoryReportBox->setVisible(false); connect(m_memoryReportBox, SIGNAL(clicked()), SLOT(showMemoryInfoToolTip())); m_pointerPositionLabel = new QLabel(QString()); m_pointerPositionLabel->setObjectName("pointerPositionLabel"); m_pointerPositionLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_pointerPositionLabel->setMinimumWidth(100); m_pointerPositionLabel->setContentsMargins(5,5, 5, 5); addStatusBarItem(m_pointerPositionLabel); m_pointerPositionLabel->setVisible(false); connect(KisMemoryStatisticsServer::instance(), SIGNAL(sigUpdateMemoryStatistics()), SLOT(imageSizeChanged())); + + m_resetAngleButton = new QToolButton; + m_resetAngleButton->setObjectName("Reset Rotation"); + m_resetAngleButton->setCheckable(false); + m_resetAngleButton->setToolTip(i18n("Reset Rotation")); + m_resetAngleButton->setAutoRaise(true); + m_resetAngleButton->setIcon(KisIconUtils::loadIcon("rotate-canvas-left")); + addStatusBarItem(m_resetAngleButton); + + connect(m_resetAngleButton, SIGNAL(clicked()), m_viewManager, SLOT(slotResetRotation())); + m_resetAngleButton->setVisible(false); + } KisStatusBar::~KisStatusBar() { } void KisStatusBar::setView(QPointer imageView) { if (m_imageView == imageView) { return; } if (m_imageView) { m_imageView->disconnect(this); removeStatusBarItem(m_imageView->zoomManager()->zoomActionWidget()); m_imageView = 0; } if (imageView) { m_imageView = imageView; m_resetAngleButton->setVisible(true); connect(m_imageView, SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), this, SLOT(updateStatusBarProfileLabel())); connect(m_imageView, SIGNAL(sigProfileChanged(const KoColorProfile*)), this, SLOT(updateStatusBarProfileLabel())); connect(m_imageView, SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SLOT(imageSizeChanged())); updateStatusBarProfileLabel(); addStatusBarItem(m_imageView->zoomManager()->zoomActionWidget()); } else { m_resetAngleButton->setVisible(false); } imageSizeChanged(); } void KisStatusBar::addStatusBarItem(QWidget *widget, int stretch, bool permanent) { StatusBarItem sbItem(widget); if (permanent) { m_statusBar->addPermanentWidget(widget, stretch); } else { m_statusBar->addWidget(widget, stretch); } widget->setVisible(true); m_statusBarItems.append(sbItem); } void KisStatusBar::removeStatusBarItem(QWidget *widget) { int i = 0; Q_FOREACH(const StatusBarItem& sbItem, m_statusBarItems) { if (sbItem.widget() == widget) { break; } i++; } if (i < m_statusBarItems.count()) { m_statusBar->removeWidget(m_statusBarItems[i].widget()); m_statusBarItems.remove(i); } } void KisStatusBar::hideAllStatusBarItems() { Q_FOREACH(const StatusBarItem& sbItem, m_statusBarItems) { sbItem.hide(); } } void KisStatusBar::showAllStatusBarItems() { Q_FOREACH(const StatusBarItem& sbItem, m_statusBarItems) { sbItem.show(); } } void KisStatusBar::documentMousePositionChanged(const QPointF &pos) { if (!m_imageView) return; QPoint pixelPos = m_imageView->image()->documentToImagePixelFloored(pos); pixelPos.setX(qBound(0, pixelPos.x(), m_viewManager->image()->width() - 1)); pixelPos.setY(qBound(0, pixelPos.y(), m_viewManager->image()->height() - 1)); m_pointerPositionLabel->setText(i18nc("@info mouse position (x, y)", "%1, %2", pixelPos.x(), pixelPos.y())); } void KisStatusBar::imageSizeChanged() { updateMemoryStatus(); QString sizeText; KisImageWSP image = m_imageView ? m_imageView->image() : 0; if (image) { qint32 w = image->width(); qint32 h = image->height(); sizeText = i18nc("@info:status width x height (file size)", "%1 &x %2 (%3)", w, h, m_shortMemoryTag); } else { sizeText = m_shortMemoryTag; } m_memoryReportBox->setIcon(m_memoryStatusIcon); m_memoryReportBox->setText(sizeText); m_memoryReportBox->setToolTip(m_longMemoryTag); } void KisStatusBar::updateSelectionIcon() { QIcon icon; if (!m_viewManager->selectionManager()->displaySelection()) { icon = KisIconUtils::loadIcon("selection-mode_invisible"); } else if (m_viewManager->selectionManager()->showSelectionAsMask()) { icon = KisIconUtils::loadIcon("selection-mode_mask"); } else /* if (!m_view->selectionManager()->showSelectionAsMask()) */ { icon = KisIconUtils::loadIcon("selection-mode_ants"); } m_selectionStatus->setIcon(icon); } void KisStatusBar::updateMemoryStatus() { KisMemoryStatisticsServer::Statistics stats = KisMemoryStatisticsServer::instance() ->fetchMemoryStatistics(m_imageView ? m_imageView->image() : 0); const KFormat format; const QString imageStatsMsg = i18nc("tooltip on statusbar memory reporting button (image stats)", "Image size:\t %1\n" " - layers:\t\t %2\n" " - projections:\t %3\n" " - instant preview:\t %4\n", format.formatByteSize(stats.imageSize), format.formatByteSize(stats.layersSize), format.formatByteSize(stats.projectionsSize), format.formatByteSize(stats.lodSize)); const QString memoryStatsMsg = i18nc("tooltip on statusbar memory reporting button (total stats)", "Memory used:\t %1 / %2\n" " image data:\t %3 / %4\n" " pool:\t\t %5 / %6\n" " undo data:\t %7\n" "\n" "Swap used:\t %8", format.formatByteSize(stats.totalMemorySize), format.formatByteSize(stats.totalMemoryLimit), format.formatByteSize(stats.realMemorySize), format.formatByteSize(stats.tilesHardLimit), format.formatByteSize(stats.poolSize), format.formatByteSize(stats.tilesPoolLimit), format.formatByteSize(stats.historicalMemorySize), format.formatByteSize(stats.swapSize)); QString longStats = imageStatsMsg + "\n" + memoryStatsMsg; QString shortStats = format.formatByteSize(stats.imageSize); QIcon icon; const qint64 warnLevel = stats.tilesHardLimit - stats.tilesHardLimit / 8; if (stats.imageSize > warnLevel || stats.realMemorySize > warnLevel) { if (!m_memoryWarningLogged) { m_memoryWarningLogged = true; KisUsageLogger::log(QString("WARNING: %1 is running out of memory:%2\n").arg(m_imageView->document()->url().toLocalFile()).arg(longStats)); } icon = KisIconUtils::loadIcon("warning"); QString suffix = i18nc("tooltip on statusbar memory reporting button", "\n\nWARNING:\tOut of memory! Swapping has been started.\n" "\t\tPlease configure more RAM for Krita in Settings dialog"); longStats += suffix; } m_shortMemoryTag = shortStats; m_longMemoryTag = longStats; m_memoryStatusIcon = icon; m_memoryReportBox->setMaximumMemory(stats.totalMemoryLimit); m_memoryReportBox->setCurrentMemory(stats.totalMemorySize); m_memoryReportBox->setImageWeight(stats.imageSize); emit memoryStatusUpdated(); } void KisStatusBar::showMemoryInfoToolTip() { QToolTip::showText(QCursor::pos(), m_memoryReportBox->toolTip(), m_memoryReportBox); } void KisStatusBar::updateSelectionToolTip() { updateSelectionIcon(); KisSelectionSP selection = m_viewManager->selection(); if (selection) { m_selectionStatus->setEnabled(true); QRect r = selection->selectedExactRect(); QString displayMode = !m_viewManager->selectionManager()->displaySelection() ? i18n("Hidden") : (m_viewManager->selectionManager()->showSelectionAsMask() ? i18n("Mask") : i18n("Ants")); m_selectionStatus->setToolTip( i18n("Selection: x = %1 y = %2 width = %3 height = %4\n" "Display Mode: %5", r.x(), r.y(), r.width(), r.height(), displayMode)); } else { m_selectionStatus->setEnabled(false); m_selectionStatus->setToolTip(i18n("No Selection")); } } void KisStatusBar::setSelection(KisImageWSP image) { Q_UNUSED(image); updateSelectionToolTip(); } void KisStatusBar::setProfile(KisImageWSP image) { if (m_statusBarProfileLabel == 0) { return; } if (!image) return; if (image->profile() == 0) { m_statusBarProfileLabel->setText(i18n("No profile")); } else { m_statusBarProfileLabel->setText(i18nc(" ", "%1 %2", image->colorSpace()->name(), image->profile()->name())); } } void KisStatusBar::setHelp(const QString &t) { Q_UNUSED(t); } void KisStatusBar::updateStatusBarProfileLabel() { if (!m_imageView) return; setProfile(m_imageView->image()); } KoProgressUpdater *KisStatusBar::progressUpdater() { return m_progressUpdater.data(); } diff --git a/libs/ui/opengl/KisOpenGLModeProber.cpp b/libs/ui/opengl/KisOpenGLModeProber.cpp index 1fe495b541..6589e67034 100644 --- a/libs/ui/opengl/KisOpenGLModeProber.cpp +++ b/libs/ui/opengl/KisOpenGLModeProber.cpp @@ -1,333 +1,333 @@ /* * 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 supoprted by this build of Krita"; + 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 supoprted by this build of Krita"; + 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(); } diff --git a/libs/ui/opengl/kis_opengl.cpp b/libs/ui/opengl/kis_opengl.cpp index bcfa3a7c9c..32171dc3c1 100644 --- a/libs/ui/opengl/kis_opengl.cpp +++ b/libs/ui/opengl/kis_opengl.cpp @@ -1,913 +1,913 @@ /* * Copyright (c) 2007 Adrian Page * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "opengl/kis_opengl.h" #include "opengl/kis_opengl_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisOpenGLModeProber.h" #include #include #include "kis_assert.h" #include #include #include #ifndef GL_RENDERER # define GL_RENDERER 0x1F01 #endif using namespace KisOpenGLPrivate; namespace { // config option, set manually by main() bool g_isDebugSynchronous = false; bool g_sanityDefaultFormatIsSet = false; boost::optional openGLCheckResult; bool g_needsFenceWorkaround = false; bool g_needsPixmapCacheWorkaround = false; QString g_surfaceFormatDetectionLog; QString g_debugText("OpenGL Info\n **OpenGL not initialized**"); QVector g_openglWarningStrings; KisOpenGL::OpenGLRenderers g_supportedRenderers; KisOpenGL::OpenGLRenderer g_rendererPreferredByQt; void overrideSupportedRenderers(KisOpenGL::OpenGLRenderers supportedRenderers, KisOpenGL::OpenGLRenderer preferredByQt) { g_supportedRenderers = supportedRenderers; g_rendererPreferredByQt = preferredByQt; } void openglOnMessageLogged(const QOpenGLDebugMessage& debugMessage) { qDebug() << "OpenGL:" << debugMessage; } KisOpenGL::OpenGLRenderer getRendererFromProbeResult(KisOpenGLModeProber::Result info) { KisOpenGL::OpenGLRenderer result = KisOpenGL::RendererDesktopGL; if (info.isOpenGLES()) { const QString rendererString = info.rendererString().toLower(); if (rendererString.contains("basic render driver") || rendererString.contains("software")) { result = KisOpenGL::RendererSoftware; } else { result = KisOpenGL::RendererOpenGLES; } } return result; } } KisOpenGLPrivate::OpenGLCheckResult::OpenGLCheckResult(QOpenGLContext &context) { if (!context.isValid()) { return; } QOpenGLFunctions *funcs = context.functions(); // funcs is ready to be used m_rendererString = QString(reinterpret_cast(funcs->glGetString(GL_RENDERER))); m_driverVersionString = QString(reinterpret_cast(funcs->glGetString(GL_VERSION))); m_glMajorVersion = context.format().majorVersion(); m_glMinorVersion = context.format().minorVersion(); m_supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions); m_isOpenGLES = context.isOpenGLES(); } void KisOpenGLPrivate::appendOpenGLWarningString(KLocalizedString warning) { g_openglWarningStrings << warning; } void KisOpenGLPrivate::overrideOpenGLWarningString(QVector warnings) { g_openglWarningStrings = warnings; } void KisOpenGL::initialize() { if (openGLCheckResult) return; KIS_SAFE_ASSERT_RECOVER_NOOP(g_sanityDefaultFormatIsSet); KisOpenGL::RendererConfig config; config.format = QSurfaceFormat::defaultFormat(); openGLCheckResult = KisOpenGLModeProber::instance()->probeFormat(config, false); g_debugText.clear(); QDebug debugOut(&g_debugText); debugOut << "OpenGL Info\n"; if (openGLCheckResult) { debugOut << "\n Vendor: " << openGLCheckResult->vendorString(); debugOut << "\n Renderer: " << openGLCheckResult->rendererString(); debugOut << "\n Version: " << openGLCheckResult->driverVersionString(); debugOut << "\n Shading language: " << openGLCheckResult->shadingLanguageString(); debugOut << "\n Requested format: " << QSurfaceFormat::defaultFormat(); debugOut << "\n Current format: " << openGLCheckResult->format(); debugOut.nospace(); debugOut << "\n Version: " << openGLCheckResult->glMajorVersion() << "." << openGLCheckResult->glMinorVersion(); debugOut.resetFormat(); debugOut << "\n Supports deprecated functions" << openGLCheckResult->supportsDeprecatedFunctions(); debugOut << "\n is OpenGL ES:" << openGLCheckResult->isOpenGLES(); } debugOut << "\n\nQPA OpenGL Detection Info"; debugOut << "\n supportsDesktopGL:" << bool(g_supportedRenderers & RendererDesktopGL); #ifdef Q_OS_WIN debugOut << "\n supportsAngleD3D11:" << bool(g_supportedRenderers & RendererOpenGLES); debugOut << "\n isQtPreferAngle:" << bool(g_rendererPreferredByQt == RendererOpenGLES); #else debugOut << "\n supportsOpenGLES:" << bool(g_supportedRenderers & RendererOpenGLES); debugOut << "\n isQtPreferOpenGLES:" << bool(g_rendererPreferredByQt == RendererOpenGLES); #endif // debugOut << "\n== log ==\n"; // debugOut.noquote(); // debugOut << g_surfaceFormatDetectionLog; // debugOut.resetFormat(); // debugOut << "\n== end log =="; dbgOpenGL.noquote().nospace() << g_debugText; - KisUsageLogger::write(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::needsFenceWorkaround() { initialize(); return g_needsFenceWorkaround; } bool KisOpenGL::needsPixmapCacheWorkaround() { initialize(); return g_needsPixmapCacheWorkaround; } void KisOpenGL::testingInitializeDefaultSurfaceFormat() { setDefaultSurfaceConfig(selectSurfaceConfig(KisOpenGL::RendererAuto, KisConfig::BT709_G22, false)); } void KisOpenGL::setDebugSynchronous(bool value) { g_isDebugSynchronous = value; } KisOpenGL::OpenGLRenderer KisOpenGL::getCurrentOpenGLRenderer() { if (!openGLCheckResult) return RendererAuto; return getRendererFromProbeResult(*openGLCheckResult); } KisOpenGL::OpenGLRenderer KisOpenGL::getQtPreferredOpenGLRenderer() { return g_rendererPreferredByQt; } KisOpenGL::OpenGLRenderers KisOpenGL::getSupportedOpenGLRenderers() { return g_supportedRenderers; } KisOpenGL::OpenGLRenderer KisOpenGL::getUserPreferredOpenGLRendererConfig() { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return convertConfigToOpenGLRenderer(kritarc.value("OpenGLRenderer", "auto").toString()); } void KisOpenGL::setUserPreferredOpenGLRendererConfig(KisOpenGL::OpenGLRenderer renderer) { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("OpenGLRenderer", KisOpenGL::convertOpenGLRendererToConfig(renderer)); } QString KisOpenGL::convertOpenGLRendererToConfig(KisOpenGL::OpenGLRenderer renderer) { switch (renderer) { case RendererNone: return QStringLiteral("none"); case RendererSoftware: return QStringLiteral("software"); case RendererDesktopGL: return QStringLiteral("desktop"); case RendererOpenGLES: return QStringLiteral("angle"); default: return QStringLiteral("auto"); } } KisOpenGL::OpenGLRenderer KisOpenGL::convertConfigToOpenGLRenderer(QString renderer) { if (renderer == "desktop") { return RendererDesktopGL; } else if (renderer == "angle") { return RendererOpenGLES; } else if (renderer == "software") { return RendererSoftware; } else if (renderer == "none") { return RendererNone; } else { return RendererAuto; } } KisOpenGL::OpenGLRenderer KisOpenGL::RendererConfig::rendererId() const { KisOpenGL::OpenGLRenderer result = RendererAuto; if (format.renderableType() == QSurfaceFormat::OpenGLES && angleRenderer == AngleRendererD3d11Warp) { result = RendererSoftware; } else if (format.renderableType() == QSurfaceFormat::OpenGLES && angleRenderer == AngleRendererD3d11) { result = RendererOpenGLES; } else if (format.renderableType() == QSurfaceFormat::OpenGL) { result = RendererDesktopGL; } else if (format.renderableType() == QSurfaceFormat::DefaultRenderableType && angleRenderer == AngleRendererD3d11) { // noop } else { qWarning() << "WARNING: unsupported combination of OpenGL renderer" << ppVar(format.renderableType()) << ppVar(angleRenderer); } return result; } namespace { typedef std::pair RendererInfo; RendererInfo getRendererInfo(KisOpenGL::OpenGLRenderer renderer) { RendererInfo info = {QSurfaceFormat::DefaultRenderableType, KisOpenGL::AngleRendererD3d11}; switch (renderer) { case KisOpenGL::RendererNone: info = {QSurfaceFormat::DefaultRenderableType, KisOpenGL::AngleRendererDefault}; break; case KisOpenGL::RendererAuto: break; case KisOpenGL::RendererDesktopGL: info = {QSurfaceFormat::OpenGL, KisOpenGL::AngleRendererD3d11}; break; case KisOpenGL::RendererOpenGLES: info = {QSurfaceFormat::OpenGLES, KisOpenGL::AngleRendererD3d11}; break; case KisOpenGL::RendererSoftware: info = {QSurfaceFormat::OpenGLES, KisOpenGL::AngleRendererD3d11Warp}; break; } return info; } KisOpenGL::RendererConfig generateSurfaceConfig(KisOpenGL::OpenGLRenderer renderer, KisConfig::RootSurfaceFormat rootSurfaceFormat, bool debugContext) { RendererInfo info = getRendererInfo(renderer); KisOpenGL::RendererConfig config; config.angleRenderer = info.second; QSurfaceFormat &format = config.format; #ifdef Q_OS_MACOS format.setVersion(3, 2); format.setProfile(QSurfaceFormat::CoreProfile); #elif !defined(Q_OS_ANDROID) // XXX This can be removed once we move to Qt5.7 format.setVersion(3, 0); format.setProfile(QSurfaceFormat::CompatibilityProfile); format.setOptions(QSurfaceFormat::DeprecatedFunctions); #endif format.setDepthBufferSize(24); format.setStencilBufferSize(8); KisOpenGLModeProber::initSurfaceFormatFromConfig(rootSurfaceFormat, &format); format.setRenderableType(info.first); format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); format.setSwapInterval(0); // Disable vertical refresh syncing if (debugContext) { format.setOption(QSurfaceFormat::DebugContext, true); } return config; } bool isOpenGLRendererBlacklisted(const QString &rendererString, const QString &driverVersionString, QVector *warningMessage) { bool isBlacklisted = false; #ifndef Q_OS_WIN Q_UNUSED(rendererString); Q_UNUSED(driverVersionString); Q_UNUSED(warningMessage); #else // Special blacklisting of OpenGL/ANGLE is tracked on: // https://phabricator.kde.org/T7411 // HACK: Specifically detect for Intel driver build number // See https://www.intel.com/content/www/us/en/support/articles/000005654/graphics-drivers.html if (rendererString.startsWith("Intel")) { KLocalizedString knownBadIntelWarning = ki18n("The Intel graphics driver in use is known to have issues with OpenGL."); KLocalizedString grossIntelWarning = ki18n( "Intel graphics drivers tend to have issues with OpenGL so ANGLE will be used by default. " "You may manually switch to OpenGL but it is not guaranteed to work properly." ); QRegularExpression regex("\\b\\d{1,2}\\.\\d{2}\\.\\d{1,3}\\.(\\d{4})\\b"); QRegularExpressionMatch match = regex.match(driverVersionString); if (match.hasMatch()) { int driverBuild = match.captured(1).toInt(); if (driverBuild > 4636 && driverBuild < 4729) { // Make ANGLE the preferred renderer for Intel driver versions // between build 4636 and 4729 (exclusive) due to an UI offset bug. // See https://communities.intel.com/thread/116003 // (Build 4636 is known to work from some test results) qDebug() << "Detected Intel driver build between 4636 and 4729, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << knownBadIntelWarning; } else if (driverBuild == 4358) { // There are several reports on a bug where the canvas is not being // updated properly which has debug info pointing to this build. qDebug() << "Detected Intel driver build 4358, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << knownBadIntelWarning; } else { // Intel tends to randomly break OpenGL in some of their new driver // builds, therefore we just shouldn't use OpenGL by default to // reduce bug report noises. qDebug() << "Detected Intel driver, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << grossIntelWarning; } } else { // In case Intel changed the driver version format to something that // we don't understand, we still select ANGLE. qDebug() << "Detected Intel driver with unknown version format, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << grossIntelWarning; } } #endif return isBlacklisted; } boost::optional orderPreference(bool lhs, bool rhs) { if (lhs == rhs) return boost::none; if (lhs && !rhs) return true; if (!lhs && rhs) return false; return false; } #define ORDER_BY(lhs, rhs) if (auto res = orderPreference((lhs), (rhs))) { return *res; } class FormatPositionLess { public: FormatPositionLess() { } bool operator()(const KisOpenGL::RendererConfig &lhs, const KisOpenGL::RendererConfig &rhs) const { KIS_SAFE_ASSERT_RECOVER_NOOP(m_preferredColorSpace != KisSurfaceColorSpace::DefaultColorSpace); if (m_preferredRendererByUser != KisOpenGL::RendererSoftware) { ORDER_BY(!isFallbackOnly(lhs.rendererId()), !isFallbackOnly(rhs.rendererId())); } #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) ORDER_BY(isPreferredColorSpace(lhs.format.colorSpace()), isPreferredColorSpace(rhs.format.colorSpace())); #endif if (doPreferHDR()) { ORDER_BY(isHDRFormat(lhs.format), isHDRFormat(rhs.format)); } else { ORDER_BY(!isHDRFormat(lhs.format), !isHDRFormat(rhs.format)); } if (m_preferredRendererByUser != KisOpenGL::RendererAuto) { ORDER_BY(lhs.rendererId() == m_preferredRendererByUser, rhs.rendererId() == m_preferredRendererByUser); } ORDER_BY(!isBlacklisted(lhs.rendererId()), !isBlacklisted(rhs.rendererId())); if (doPreferHDR() && m_preferredRendererByHDR != KisOpenGL::RendererAuto) { ORDER_BY(lhs.rendererId() == m_preferredRendererByHDR, rhs.rendererId() == m_preferredRendererByHDR); } KIS_SAFE_ASSERT_RECOVER_NOOP(m_preferredRendererByQt != KisOpenGL::RendererAuto); ORDER_BY(lhs.rendererId() == m_preferredRendererByQt, rhs.rendererId() == m_preferredRendererByQt); return false; } public: void setPreferredColorSpace(const KisSurfaceColorSpace &preferredColorSpace) { m_preferredColorSpace = preferredColorSpace; } void setPreferredRendererByQt(const KisOpenGL::OpenGLRenderer &preferredRendererByQt) { m_preferredRendererByQt = preferredRendererByQt; } void setPreferredRendererByUser(const KisOpenGL::OpenGLRenderer &preferredRendererByUser) { m_preferredRendererByUser = preferredRendererByUser; } void setPreferredRendererByHDR(const KisOpenGL::OpenGLRenderer &preferredRendererByHDR) { m_preferredRendererByHDR = preferredRendererByHDR; } void setOpenGLBlacklisted(bool openGLBlacklisted) { m_openGLBlacklisted = openGLBlacklisted; } void setOpenGLESBlacklisted(bool openGLESBlacklisted) { m_openGLESBlacklisted = openGLESBlacklisted; } bool isOpenGLBlacklisted() const { return m_openGLBlacklisted; } bool isOpenGLESBlacklisted() const { return m_openGLESBlacklisted; } KisSurfaceColorSpace preferredColorSpace() const { return m_preferredColorSpace; } KisOpenGL::OpenGLRenderer preferredRendererByUser() const { return m_preferredRendererByUser; } private: bool isHDRFormat(const QSurfaceFormat &f) const { #ifdef HAVE_HDR return f.colorSpace() == KisSurfaceColorSpace::bt2020PQColorSpace || f.colorSpace() == KisSurfaceColorSpace::scRGBColorSpace; #else Q_UNUSED(f); return false; #endif } bool isFallbackOnly(KisOpenGL::OpenGLRenderer r) const { return r == KisOpenGL::RendererSoftware; } bool isBlacklisted(KisOpenGL::OpenGLRenderer r) const { KIS_SAFE_ASSERT_RECOVER_NOOP(r == KisOpenGL::RendererAuto || r == KisOpenGL::RendererDesktopGL || r == KisOpenGL::RendererOpenGLES || r == KisOpenGL::RendererSoftware || r == KisOpenGL::RendererNone); return (r == KisOpenGL::RendererDesktopGL && m_openGLBlacklisted) || (r == KisOpenGL::RendererOpenGLES && m_openGLESBlacklisted) || (r == KisOpenGL::RendererSoftware && m_openGLESBlacklisted); } bool doPreferHDR() const { #ifdef HAVE_HDR return m_preferredColorSpace == KisSurfaceColorSpace::bt2020PQColorSpace || m_preferredColorSpace == KisSurfaceColorSpace::scRGBColorSpace; #else return false; #endif } bool isPreferredColorSpace(const KisSurfaceColorSpace cs) const { return KisOpenGLModeProber::fuzzyCompareColorSpaces(m_preferredColorSpace, cs); return false; } private: KisSurfaceColorSpace m_preferredColorSpace = KisSurfaceColorSpace::DefaultColorSpace; KisOpenGL::OpenGLRenderer m_preferredRendererByQt = KisOpenGL::RendererDesktopGL; KisOpenGL::OpenGLRenderer m_preferredRendererByUser = KisOpenGL::RendererAuto; KisOpenGL::OpenGLRenderer m_preferredRendererByHDR = KisOpenGL::RendererAuto; bool m_openGLBlacklisted = false; bool m_openGLESBlacklisted = false; }; struct DetectionDebug : public QDebug { DetectionDebug(QString *string) : QDebug(string), m_string(string), m_originalSize(string->size()) {} ~DetectionDebug() { dbgOpenGL << m_string->right(m_string->size() - m_originalSize); *this << endl; } QString *m_string; int m_originalSize; }; } #define dbgDetection() DetectionDebug(&g_surfaceFormatDetectionLog) KisOpenGL::RendererConfig KisOpenGL::selectSurfaceConfig(KisOpenGL::OpenGLRenderer preferredRenderer, KisConfig::RootSurfaceFormat preferredRootSurfaceFormat, bool enableDebug) { QVector warningMessages; using Info = boost::optional; QHash renderersToTest; renderersToTest.insert(RendererDesktopGL, Info()); renderersToTest.insert(RendererOpenGLES, Info()); #ifdef Q_OS_WIN renderersToTest.insert(RendererSoftware, Info()); #endif #ifdef HAVE_HDR QVector formatSymbols({KisConfig::BT709_G22, KisConfig::BT709_G10, KisConfig::BT2020_PQ}); #else QVector formatSymbols({KisConfig::BT709_G22}); #endif KisOpenGL::RendererConfig defaultConfig = generateSurfaceConfig(KisOpenGL::RendererAuto, KisConfig::BT709_G22, false); Info info = KisOpenGLModeProber::instance()->probeFormat(defaultConfig); #ifdef Q_OS_WIN if (!info) { // try software rasterizer (WARP) defaultConfig = generateSurfaceConfig(KisOpenGL::RendererSoftware, KisConfig::BT709_G22, false); info = KisOpenGLModeProber::instance()->probeFormat(defaultConfig); if (!info) { renderersToTest.remove(RendererSoftware); } } #endif if (!info) return KisOpenGL::RendererConfig(); const OpenGLRenderer defaultRenderer = getRendererFromProbeResult(*info); OpenGLRenderers supportedRenderers = RendererNone; FormatPositionLess compareOp; compareOp.setPreferredRendererByQt(defaultRenderer); #ifdef HAVE_HDR compareOp.setPreferredColorSpace( preferredRootSurfaceFormat == KisConfig::BT709_G22 ? KisSurfaceColorSpace::sRGBColorSpace : preferredRootSurfaceFormat == KisConfig::BT709_G10 ? KisSurfaceColorSpace::scRGBColorSpace : KisSurfaceColorSpace::bt2020PQColorSpace); #else Q_UNUSED(preferredRootSurfaceFormat); compareOp.setPreferredColorSpace(KisSurfaceColorSpace::sRGBColorSpace); #endif #ifdef Q_OS_WIN compareOp.setPreferredRendererByHDR(KisOpenGL::RendererOpenGLES); #endif compareOp.setPreferredRendererByUser(preferredRenderer); compareOp.setOpenGLESBlacklisted(false); // We cannot blacklist ES drivers atm renderersToTest[defaultRenderer] = info; for (auto it = renderersToTest.begin(); it != renderersToTest.end(); ++it) { Info info = it.value(); if (!info) { info = KisOpenGLModeProber::instance()-> probeFormat(generateSurfaceConfig(it.key(), KisConfig::BT709_G22, false)); *it = info; } compareOp.setOpenGLBlacklisted( !info || isOpenGLRendererBlacklisted(info->rendererString(), info->driverVersionString(), &warningMessages)); if (info && info->isSupportedVersion()) { supportedRenderers |= it.key(); } } OpenGLRenderer preferredByQt = defaultRenderer; if (preferredByQt == RendererDesktopGL && supportedRenderers & RendererDesktopGL && compareOp.isOpenGLBlacklisted()) { preferredByQt = RendererOpenGLES; } else if (preferredByQt == RendererOpenGLES && supportedRenderers & RendererOpenGLES && compareOp.isOpenGLESBlacklisted()) { preferredByQt = RendererDesktopGL; } QVector preferredConfigs; for (auto it = renderersToTest.begin(); it != renderersToTest.end(); ++it) { // if default mode of the renderer doesn't work, then custom won't either if (!it.value()) continue; Q_FOREACH (const KisConfig::RootSurfaceFormat formatSymbol, formatSymbols) { preferredConfigs << generateSurfaceConfig(it.key(), formatSymbol, enableDebug); } } std::stable_sort(preferredConfigs.begin(), preferredConfigs.end(), compareOp); dbgDetection() << "Supported renderers:" << supportedRenderers; dbgDetection() << "Surface format preference list:"; Q_FOREACH (const KisOpenGL::RendererConfig &config, preferredConfigs) { dbgDetection() << "*" << config.format; dbgDetection() << " " << config.rendererId(); } KisOpenGL::RendererConfig resultConfig = defaultConfig; if (preferredRenderer != RendererNone) { Q_FOREACH (const KisOpenGL::RendererConfig &config, preferredConfigs) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) dbgDetection() <<"Probing format..." << config.format.colorSpace() << config.rendererId(); #else dbgDetection() <<"Probing format..." << config.rendererId(); #endif Info info = KisOpenGLModeProber::instance()->probeFormat(config); if (info && info->isSupportedVersion()) { #ifdef Q_OS_WIN // HACK: Block ANGLE with Direct3D9 // Direct3D9 does not give OpenGL ES 3.0 // Some versions of ANGLE returns OpenGL version 3.0 incorrectly if (info->isUsingAngle() && info->rendererString().contains("Direct3D9", Qt::CaseInsensitive)) { dbgDetection() << "Skipping Direct3D 9 Angle implementation, it shouldn't have happened."; continue; } #endif dbgDetection() << "Found format:" << config.format; dbgDetection() << " " << config.rendererId(); resultConfig = config; break; } } { const bool colorSpaceIsCorrect = #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) KisOpenGLModeProber::fuzzyCompareColorSpaces(compareOp.preferredColorSpace(), resultConfig.format.colorSpace()); #else true; #endif const bool rendererIsCorrect = compareOp.preferredRendererByUser() == KisOpenGL::RendererAuto || compareOp.preferredRendererByUser() == resultConfig.rendererId(); if (!rendererIsCorrect && colorSpaceIsCorrect) { warningMessages << ki18n("Preferred renderer doesn't support requested surface format. Another renderer has been selected."); } else if (!colorSpaceIsCorrect) { warningMessages << ki18n("Preferred output format is not supported by available renderers"); } } } else { resultConfig.format = QSurfaceFormat(); resultConfig.angleRenderer = AngleRendererDefault; } overrideSupportedRenderers(supportedRenderers, preferredByQt); overrideOpenGLWarningString(warningMessages); return resultConfig; } void KisOpenGL::setDefaultSurfaceConfig(const KisOpenGL::RendererConfig &config) { KIS_SAFE_ASSERT_RECOVER_NOOP(!g_sanityDefaultFormatIsSet); g_sanityDefaultFormatIsSet = true; QSurfaceFormat::setDefaultFormat(config.format); #ifdef Q_OS_WIN // Force ANGLE to use Direct3D11. D3D9 doesn't support OpenGL ES 3 and WARP // might get weird crashes atm. qputenv("QT_ANGLE_PLATFORM", KisOpenGLModeProber::angleRendererToString(config.angleRenderer).toLatin1()); #endif if (config.format.renderableType() == QSurfaceFormat::OpenGLES) { QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true); } else if (config.format.renderableType() == QSurfaceFormat::OpenGL) { QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL, true); } } bool KisOpenGL::hasOpenGL() { return openGLCheckResult->isSupportedVersion(); } diff --git a/libs/ui/osx.mm b/libs/ui/osx.mm index bf2bc26d10..ebf2206d44 100644 --- a/libs/ui/osx.mm +++ b/libs/ui/osx.mm @@ -1,32 +1,32 @@ /* * Copyright (c) 2017 Bernhard Liebl * * 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. */ -#import +#import extern "C" { bool isMouseCoalescingEnabled(); void setMouseCoalescingEnabled(bool enabled); } bool isMouseCoalescingEnabled() { return NSEvent.isMouseCoalescingEnabled; } void setMouseCoalescingEnabled(bool enabled) { NSEvent.mouseCoalescingEnabled = enabled; } diff --git a/libs/ui/qtsingleapplication/qtlocalpeer.cpp b/libs/ui/qtsingleapplication/qtlocalpeer.cpp index 76761e28dd..d6eeae213c 100644 --- a/libs/ui/qtsingleapplication/qtlocalpeer.cpp +++ b/libs/ui/qtsingleapplication/qtlocalpeer.cpp @@ -1,180 +1,180 @@ /**************************************************************************** ** ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and -** conditions see http://www.qt.io/licensing. For further information -** use the contact form at http://www.qt.io/contact-us. +** conditions see https://www.qt.io/licensing. For further information +** use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include "qtlocalpeer.h" #include #include #include #if defined(Q_OS_WIN) #include #include typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*); static PProcessIdToSessionId pProcessIdToSessionId = 0; #endif #if defined(Q_OS_UNIX) #include #include #endif static const char ack[] = "ack"; QString QtLocalPeer::appSessionId(const QString &appId) { QByteArray idc = appId.toUtf8(); quint16 idNum = qChecksum(idc.constData(), idc.size()); //### could do: two 16bit checksums over separate halves of id, for a 32bit result - improved uniqeness probability. Every-other-char split would be best. QString res = QLatin1String("qtsingleapplication-") + QString::number(idNum, 16); #if defined(Q_OS_WIN) if (!pProcessIdToSessionId) { QLibrary lib(QLatin1String("kernel32")); pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId"); } if (pProcessIdToSessionId) { DWORD sessionId = 0; pProcessIdToSessionId(GetCurrentProcessId(), &sessionId); res += QLatin1Char('-') + QString::number(sessionId, 16); } #else res += QLatin1Char('-') + QString::number(::getuid(), 16); #endif return res; } QtLocalPeer::QtLocalPeer(QObject *parent, const QString &appId) : QObject(parent), id(appId) { if (id.isEmpty()) id = QCoreApplication::applicationFilePath(); //### On win, check if this returns .../argv[0] without casefolding; .\MYAPP == .\myapp on Win socketName = appSessionId(id); server = new QLocalServer(this); QString lockName = QDir(QDir::tempPath()).absolutePath() + QLatin1Char('/') + socketName + QLatin1String("-lockfile"); lockFile.setFileName(lockName); lockFile.open(QIODevice::ReadWrite); } bool QtLocalPeer::isClient() { if (lockFile.isLocked()) return false; if (!lockFile.lock(QtLockedFile::WriteLock, false)) return true; if (!QLocalServer::removeServer(socketName)) qWarning("QtSingleCoreApplication: could not cleanup socket"); bool res = server->listen(socketName); if (!res) qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString())); QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection())); return false; } bool QtLocalPeer::sendMessage(const QByteArray &message, int timeout, bool block) { if (!isClient()) return false; QLocalSocket socket; bool connOk = false; for (int i = 0; i < 2; i++) { // Try twice, in case the other instance is just starting up socket.connectToServer(socketName); connOk = socket.waitForConnected(timeout/2); if (connOk || i) break; int ms = 250; #if defined(Q_OS_WIN) Sleep(DWORD(ms)); #else struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 }; nanosleep(&ts, 0); #endif } if (!connOk) return false; QByteArray uMsg(message); QDataStream ds(&socket); ds.writeBytes(uMsg.constData(), uMsg.size()); bool res = socket.waitForBytesWritten(timeout); res &= socket.waitForReadyRead(timeout); // wait for ack res &= (socket.read(qstrlen(ack)) == ack); if (block) // block until peer disconnects socket.waitForDisconnected(-1); return res; } void QtLocalPeer::receiveConnection() { QLocalSocket* socket = server->nextPendingConnection(); if (!socket) return; // Why doesn't Qt have a blocking stream that takes care of this shait??? while (socket->bytesAvailable() < static_cast(sizeof(quint32))) { if (!socket->isValid()) // stale request return; socket->waitForReadyRead(1000); } QDataStream ds(socket); QByteArray uMsg; quint32 remaining; ds >> remaining; uMsg.resize(remaining); int got = 0; char* uMsgBuf = uMsg.data(); //dbgKrita << "RCV: remaining" << remaining; do { got = ds.readRawData(uMsgBuf, remaining); remaining -= got; uMsgBuf += got; //qDebug() << "RCV: got" << got << "remaining" << remaining; } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); //### error check: got<0 if (got < 0) { qWarning() << "QtLocalPeer: Message reception failed" << socket->errorString(); delete socket; return; } // ### async this socket->write(ack, qstrlen(ack)); socket->waitForBytesWritten(1000); emit messageReceived(uMsg, socket); // ##(might take a long time to return) } diff --git a/libs/ui/qtsingleapplication/qtlocalpeer.h b/libs/ui/qtsingleapplication/qtlocalpeer.h index 5a48afc37e..d103781c96 100644 --- a/libs/ui/qtsingleapplication/qtlocalpeer.h +++ b/libs/ui/qtsingleapplication/qtlocalpeer.h @@ -1,62 +1,62 @@ /**************************************************************************** ** ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and -** conditions see http://www.qt.io/licensing. For further information -** use the contact form at http://www.qt.io/contact-us. +** conditions see https://www.qt.io/licensing. For further information +** use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include #include #include #include class QtLocalPeer : public QObject { Q_OBJECT public: explicit QtLocalPeer(QObject *parent = 0, const QString &appId = QString()); bool isClient(); bool sendMessage(const QByteArray &message, int timeout, bool block); QString applicationId() const { return id; } static QString appSessionId(const QString &appId); Q_SIGNALS: void messageReceived(const QByteArray &message, QObject *socket); protected Q_SLOTS: void receiveConnection(); protected: QString id; QString socketName; QLocalServer* server; QtLockedFile lockFile; }; diff --git a/libs/ui/qtsingleapplication/qtsingleapplication.cpp b/libs/ui/qtsingleapplication/qtsingleapplication.cpp index 6345b807fb..c0be620928 100644 --- a/libs/ui/qtsingleapplication/qtsingleapplication.cpp +++ b/libs/ui/qtsingleapplication/qtsingleapplication.cpp @@ -1,192 +1,192 @@ /**************************************************************************** ** ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and -** conditions see http://www.qt.io/licensing. For further information -** use the contact form at http://www.qt.io/contact-us. +** conditions see https://www.qt.io/licensing. For further information +** use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include "qtsingleapplication.h" #include "qtlocalpeer.h" #include #include #include #include #include static const int instancesSize = 1024; static QString instancesLockFilename(const QString &appSessionId) { const QChar slash(QLatin1Char('/')); QString res = QDir::tempPath(); if (!res.endsWith(slash)) res += slash; return res + appSessionId + QLatin1String("-instances"); } QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv) : QApplication(argc, argv), firstPeer(-1), pidPeer(0) { this->appId = appId; const QString appSessionId = QtLocalPeer::appSessionId(appId); // This shared memory holds a zero-terminated array of active (or crashed) instances instances = new QSharedMemory(appSessionId, this); actWin = 0; block = false; // First instance creates the shared memory, later instances attach to it const bool created = instances->create(instancesSize); if (!created) { if (!instances->attach()) { qWarning() << "Failed to initialize instances shared memory: " << instances->errorString(); delete instances; instances = 0; return; } } // QtLockedFile is used to workaround QTBUG-10364 QtLockedFile lockfile(instancesLockFilename(appSessionId)); lockfile.open(QtLockedFile::ReadWrite); lockfile.lock(QtLockedFile::WriteLock); qint64 *pids = static_cast(instances->data()); if (!created) { // Find the first instance that it still running // The whole list needs to be iterated in order to append to it for (; *pids; ++pids) { if (firstPeer == -1 && isRunning(*pids)) firstPeer = *pids; } } // Add current pid to list and terminate it *pids++ = QCoreApplication::applicationPid(); *pids = 0; pidPeer = new QtLocalPeer(this, appId + QLatin1Char('-') + QString::number(QCoreApplication::applicationPid())); connect(pidPeer, SIGNAL(messageReceived(QByteArray,QObject*)), SIGNAL(messageReceived(QByteArray,QObject*))); pidPeer->isClient(); lockfile.unlock(); } QtSingleApplication::~QtSingleApplication() { if (!instances) return; const qint64 appPid = QCoreApplication::applicationPid(); QtLockedFile lockfile(instancesLockFilename(QtLocalPeer::appSessionId(appId))); lockfile.open(QtLockedFile::ReadWrite); lockfile.lock(QtLockedFile::WriteLock); // Rewrite array, removing current pid and previously crashed ones qint64 *pids = static_cast(instances->data()); qint64 *newpids = pids; for (; *pids; ++pids) { if (*pids != appPid && isRunning(*pids)) *newpids++ = *pids; } *newpids = 0; lockfile.unlock(); } bool QtSingleApplication::event(QEvent *event) { if (event->type() == QEvent::FileOpen) { QFileOpenEvent *foe = static_cast(event); emit fileOpenRequest(foe->file()); return true; } return QApplication::event(event); } bool QtSingleApplication::isRunning(qint64 pid) { if (pid == -1) { pid = firstPeer; if (pid == -1) { return false; } } QtLocalPeer peer(this, appId + QLatin1Char('-') + QString::number(pid, 10)); return peer.isClient(); } bool QtSingleApplication::sendMessage(const QByteArray &message, int timeout, qint64 pid) { if (pid == -1) { pid = firstPeer; if (pid == -1) return false; } QtLocalPeer peer(this, appId + QLatin1Char('-') + QString::number(pid, 10)); return peer.sendMessage(message, timeout, block); } QString QtSingleApplication::applicationId() const { return appId; } void QtSingleApplication::setBlock(bool value) { block = value; } void QtSingleApplication::setActivationWindow(QWidget *aw, bool activateOnMessage) { actWin = aw; if (!pidPeer) return; if (activateOnMessage) connect(pidPeer, SIGNAL(messageReceived(QByteArray,QObject*)), this, SLOT(activateWindow())); else disconnect(pidPeer, SIGNAL(messageReceived(QByteArray,QObject*)), this, SLOT(activateWindow())); } QWidget* QtSingleApplication::activationWindow() const { return actWin; } void QtSingleApplication::activateWindow() { if (actWin) { actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized); actWin->raise(); actWin->activateWindow(); } } diff --git a/libs/ui/qtsingleapplication/qtsingleapplication.h b/libs/ui/qtsingleapplication/qtsingleapplication.h index 54261424f0..6fb39e15d0 100644 --- a/libs/ui/qtsingleapplication/qtsingleapplication.h +++ b/libs/ui/qtsingleapplication/qtsingleapplication.h @@ -1,79 +1,79 @@ /**************************************************************************** ** ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and -** conditions see http://www.qt.io/licensing. For further information -** use the contact form at http://www.qt.io/contact-us. +** conditions see https://www.qt.io/licensing. For further information +** use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #ifndef QTSINGLEAPPLICATION #define QTSINGLEAPPLICATION #include QT_FORWARD_DECLARE_CLASS(QSharedMemory) class QtLocalPeer; #include class KRITAUI_EXPORT QtSingleApplication : public QApplication { Q_OBJECT public: QtSingleApplication(const QString &id, int &argc, char **argv); ~QtSingleApplication() override; bool isRunning(qint64 pid = -1); void setActivationWindow(QWidget* aw, bool activateOnMessage = true); QWidget* activationWindow() const; bool event(QEvent *event) override; QString applicationId() const; void setBlock(bool value); public Q_SLOTS: bool sendMessage(const QByteArray &message, int timeout = 5000, qint64 pid = -1); void activateWindow(); Q_SIGNALS: void messageReceived(const QByteArray &message, QObject *socket); void fileOpenRequest(const QString &file); private: QString instancesFileName(const QString &appId); qint64 firstPeer; QSharedMemory *instances; QtLocalPeer *pidPeer; QWidget *actWin; QString appId; bool block; }; #endif diff --git a/libs/ui/tests/CMakeLists.txt b/libs/ui/tests/CMakeLists.txt index 99d892a829..e3da0f545c 100644 --- a/libs/ui/tests/CMakeLists.txt +++ b/libs/ui/tests/CMakeLists.txt @@ -1,183 +1,170 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories(${CMAKE_SOURCE_DIR}/libs/image/metadata ${CMAKE_SOURCE_DIR}/sdk/tests ) include(ECMAddTests) macro_add_unittest_definitions() ecm_add_tests( kis_image_view_converter_test.cpp kis_shape_selection_test.cpp kis_doc2_test.cpp kis_coordinates_converter_test.cpp kis_grid_config_test.cpp kis_stabilized_events_sampler_test.cpp kis_brush_hud_properties_config_test.cpp kis_shape_commands_test.cpp - kis_shape_layer_test.cpp kis_stop_gradient_editor_test.cpp kis_file_layer_test.cpp kis_multinode_property_test.cpp KisFrameSerializerTest.cpp KisFrameCacheStoreTest.cpp kis_animation_exporter_test.cpp kis_prescaled_projection_test.cpp kis_asl_layer_style_serializer_test.cpp kis_animation_importer_test.cpp KisSpinBoxSplineUnitConverterTest.cpp KisDocumentReplaceTest.cpp LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-" ) ecm_add_test( kis_selection_decoration_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp TEST_NAME KisSelectionDecorationTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_node_dummies_graph_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisNodeDummiesGraphTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_node_shapes_graph_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisNodeShapesGraphTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_model_index_converter_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisModelIndexConverterTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_categorized_list_model_test.cpp modeltest.cpp TEST_NAME KisCategorizedListModelTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_node_juggler_compressed_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisNodeJugglerCompressedTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_input_manager_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisInputManagerTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ecm_add_test( kis_node_model_test.cpp modeltest.cpp TEST_NAME kis_node_model_test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") ##### Tests that currently fail and should be fixed ##### include(KritaAddBrokenUnitTest) - -krita_add_broken_unit_test( - kis_shape_controller_test.cpp kis_dummies_facade_base_test.cpp +krita_add_broken_unit_test( kis_shape_controller_test.cpp kis_dummies_facade_base_test.cpp TEST_NAME kis_shape_controller_test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") -krita_add_broken_unit_test( - kis_exiv2_test.cpp +krita_add_broken_unit_test( kis_exiv2_test.cpp TEST_NAME KisExiv2Test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") -krita_add_broken_unit_test( - kis_clipboard_test.cpp +krita_add_broken_unit_test( kis_clipboard_test.cpp TEST_NAME KisClipboardTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") -krita_add_broken_unit_test( - freehand_stroke_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp +krita_add_broken_unit_test( freehand_stroke_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME FreehandStrokeTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") -krita_add_broken_unit_test( - FreehandStrokeBenchmark.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp +krita_add_broken_unit_test( FreehandStrokeBenchmark.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME FreehandStrokeBenchmark LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") -krita_add_broken_unit_test( - KisPaintOnTransparencyMaskTest.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp +krita_add_broken_unit_test( KisPaintOnTransparencyMaskTest.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME KisPaintOnTransparencyMaskTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") -ecm_add_test( - fill_processing_visitor_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp +krita_add_broken_unit_test( fill_processing_visitor_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME FillProcessingVisitorTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") -krita_add_broken_unit_test( - filter_stroke_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp +krita_add_broken_unit_test( filter_stroke_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp TEST_NAME FilterStrokeTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") -krita_add_broken_unit_test( - kis_selection_manager_test.cpp +krita_add_broken_unit_test( kis_selection_manager_test.cpp TEST_NAME KisSelectionManagerTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") #set_tests_properties(libs-ui-KisSelectionManagerTest PROPERTIES TIMEOUT 300) -krita_add_broken_unit_test( - kis_node_manager_test.cpp +krita_add_broken_unit_test( kis_node_manager_test.cpp TEST_NAME KisNodeManagerTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") -krita_add_broken_unit_test( - kis_dummies_facade_test.cpp kis_dummies_facade_base_test.cpp ../../../sdk/tests/testutil.cpp +krita_add_broken_unit_test( kis_dummies_facade_test.cpp kis_dummies_facade_base_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisDummiesFacadeTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") -krita_add_broken_unit_test( - kis_zoom_and_pan_test.cpp ../../../sdk/tests/testutil.cpp +krita_add_broken_unit_test( kis_zoom_and_pan_test.cpp ../../../sdk/tests/testutil.cpp TEST_NAME KisZoomAndPanTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") #set_tests_properties(libs-ui-KisZoomAndPanTest PROPERTIES TIMEOUT 300) -krita_add_broken_unit_test( - kis_action_manager_test.cpp +krita_add_broken_unit_test( kis_action_manager_test.cpp TEST_NAME KisActionManagerTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") -krita_add_broken_unit_test( - kis_categories_mapper_test.cpp testing_categories_mapper.cpp +krita_add_broken_unit_test( kis_categories_mapper_test.cpp testing_categories_mapper.cpp TEST_NAME KisCategoriesMapperTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") -krita_add_broken_unit_test( - kis_animation_frame_cache_test.cpp +krita_add_broken_unit_test( kis_animation_frame_cache_test.cpp TEST_NAME kis_animation_frame_cache_test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") -krita_add_broken_unit_test( - kis_derived_resources_test.cpp +krita_add_broken_unit_test( kis_derived_resources_test.cpp TEST_NAME kis_derived_resources_test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "libs-ui-") + +krita_add_broken_unit_test( kis_shape_layer_test.cpp + TEST_NAME KisShapeLayerTest + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "libs-ui-") diff --git a/libs/ui/tests/kis_shape_selection_test.cpp b/libs/ui/tests/kis_shape_selection_test.cpp index ffc066b62a..d49cffac52 100644 --- a/libs/ui/tests/kis_shape_selection_test.cpp +++ b/libs/ui/tests/kis_shape_selection_test.cpp @@ -1,192 +1,192 @@ /* * Copyright (c) 2008 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shape_selection_test.h" #include #include #include #include #include #include #include "kis_selection.h" #include "kis_pixel_selection.h" #include "flake/kis_shape_selection.h" #include "kis_image.h" #include "testutil.h" #include "kistest.h" #include #include #include "kis_transaction.h" void KisShapeSelectionTest::testAddChild() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QScopedPointer doc(KisPart::instance()->createDocument()); QColor qc(Qt::white); qc.setAlpha(0); KoColor bgColor(qc, cs); doc->newImage("test", 300, 300, cs, bgColor, KisConfig::CANVAS_COLOR, 1, "test", 100); KisImageSP image = doc->image(); KisSelectionSP selection = new KisSelection(); QVERIFY(selection->hasPixelSelection() == false); QVERIFY(selection->hasShapeSelection() == false); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); pixelSelection->select(QRect(0, 0, 100, 100)); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 25, 25), MAX_SELECTED); QCOMPARE(selection->selectedExactRect(), QRect(0, 0, 100, 100)); QRectF rect(50, 50, 100, 100); QTransform matrix; matrix.scale(1 / image->xRes(), 1 / image->yRes()); rect = matrix.mapRect(rect); KoPathShape* shape = new KoPathShape(); shape->setShapeId(KoPathShapeId); shape->moveTo(rect.topLeft()); shape->lineTo(rect.topRight()); shape->lineTo(rect.bottomRight()); shape->lineTo(rect.bottomLeft()); shape->close(); KisShapeSelection * shapeSelection = new KisShapeSelection(doc->shapeController(), image, selection); selection->setShapeSelection(shapeSelection); shapeSelection->addShape(shape); QVERIFY(selection->hasShapeSelection()); selection->updateProjection(); image->waitForDone(); QCOMPARE(selection->selectedExactRect(), QRect(50, 50, 100, 100)); } KoPathShape *createRectangularShape(const QRectF &rect) { KoPathShape* shape = new KoPathShape(); shape->setShapeId(KoPathShapeId); shape->moveTo(rect.topLeft()); shape->lineTo(rect.topRight()); shape->lineTo(rect.bottomRight()); shape->lineTo(rect.bottomLeft()); shape->close(); return shape; } void KisShapeSelectionTest::testUndoFlattening() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QScopedPointer doc(KisPart::instance()->createDocument()); KoColor bgColor(QColor(255, 255, 255, 0), cs); doc->newImage("test", 300, 300, cs, bgColor, KisConfig::CANVAS_COLOR, 1, "test", 100); KisImageSP image = doc->image(); QCOMPARE(image->locked(), false); KisSelectionSP selection = new KisSelection(); QCOMPARE(selection->hasPixelSelection(), false); QCOMPARE(selection->hasShapeSelection(), false); selection->setParentNode(image->root()); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); pixelSelection->select(QRect(0, 0, 100, 100)); QCOMPARE(TestUtil::alphaDevicePixel(pixelSelection, 25, 25), MAX_SELECTED); QCOMPARE(selection->selectedExactRect(), QRect(0, 0, 100, 100)); QTransform matrix; matrix.scale(1 / image->xRes(), 1 / image->yRes()); const QRectF srcRect1(50, 50, 100, 100); const QRectF rect1 = matrix.mapRect(srcRect1); KisShapeSelection * shapeSelection = new KisShapeSelection(doc->shapeController(), image, selection); selection->setShapeSelection(shapeSelection); KoPathShape *shape1 = createRectangularShape(rect1); shapeSelection->addShape(shape1); QVERIFY(selection->hasShapeSelection()); selection->pixelSelection()->clear(); - QCOMPARE(selection->selectedExactRect(), QRectF()); + QCOMPARE(selection->selectedExactRect(), QRect()); selection->updateProjection(); image->waitForDone(); - QCOMPARE(selection->selectedExactRect(), srcRect1); + QCOMPARE(selection->selectedExactRect(), srcRect1.toRect()); QCOMPARE(selection->outlineCacheValid(), true); QCOMPARE(selection->outlineCache().boundingRect(), srcRect1); QCOMPARE(selection->hasShapeSelection(), true); KisTransaction t1(selection->pixelSelection()); selection->pixelSelection()->clear(); KUndo2Command *cmd1 = t1.endAndTake(); QTest::qWait(400); image->waitForDone(); QCOMPARE(selection->selectedExactRect(), QRect()); QCOMPARE(selection->outlineCacheValid(), true); QCOMPARE(selection->outlineCache().boundingRect(), QRectF()); QCOMPARE(selection->hasShapeSelection(), false); const QRectF srcRect2(10, 10, 20, 20); const QRectF rect2 = matrix.mapRect(srcRect2); KoPathShape *shape2 = createRectangularShape(rect2); shapeSelection->addShape(shape2); QTest::qWait(400); image->waitForDone(); - QCOMPARE(selection->selectedExactRect(), srcRect2); + QCOMPARE(selection->selectedExactRect(), srcRect2.toRect()); QCOMPARE(selection->outlineCacheValid(), true); QCOMPARE(selection->outlineCache().boundingRect(), srcRect2); QCOMPARE(selection->hasShapeSelection(), true); shapeSelection->removeShape(shape2); QTest::qWait(400); image->waitForDone(); QCOMPARE(selection->selectedExactRect(), QRect()); QCOMPARE(selection->outlineCacheValid(), true); QCOMPARE(selection->outlineCache().boundingRect(), QRectF()); QCOMPARE(selection->hasShapeSelection(), false); cmd1->undo(); QTest::qWait(400); image->waitForDone(); - QCOMPARE(selection->selectedExactRect(), srcRect1); + QCOMPARE(selection->selectedExactRect(), srcRect1.toRect()); QCOMPARE(selection->outlineCacheValid(), true); QCOMPARE(selection->outlineCache().boundingRect(), srcRect1); QCOMPARE(selection->hasShapeSelection(), true); } KISTEST_MAIN(KisShapeSelectionTest) diff --git a/libs/ui/thememanager.cpp b/libs/ui/thememanager.cpp index 52c7995f18..d4013c2916 100644 --- a/libs/ui/thememanager.cpp +++ b/libs/ui/thememanager.cpp @@ -1,312 +1,312 @@ /* ============================================================ * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * Date : 2004-08-02 * Description : theme manager * * Copyright (C) 2006-2011 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "thememanager.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include #include #include #include #include // Calligra #include #ifdef __APPLE__ #include #endif namespace Digikam { // --------------------------------------------------------------- class ThemeManager::ThemeManagerPriv { public: ThemeManagerPriv() : themeMenuActionGroup(0) , themeMenuAction(0) { } QString currentThemeName; QMap themeMap; // map QActionGroup* themeMenuActionGroup; KActionMenu* themeMenuAction; }; ThemeManager::ThemeManager(const QString &theme, QObject *parent) : QObject(parent) , d(new ThemeManagerPriv) { //qDebug() << "Creating theme manager with theme" << theme; d->currentThemeName = theme; populateThemeMap(); } ThemeManager::~ThemeManager() { delete d; } QString ThemeManager::currentThemeName() const { //qDebug() << "getting current themename"; QString themeName; if (d->themeMenuAction && d->themeMenuActionGroup) { QAction* action = d->themeMenuActionGroup->checkedAction(); if (action) { themeName = action->text().remove('&'); } //qDebug() << "\tthemename from action" << themeName; } else if (!d->currentThemeName.isEmpty()) { //qDebug() << "\tcurrent themename" << d->currentThemeName; themeName = d->currentThemeName; } else { //qDebug() << "\tfallback"; themeName = "Krita dark"; } //qDebug() << "\tresult" << themeName; return themeName; } void ThemeManager::setCurrentTheme(const QString& name) { //qDebug() << "setCurrentTheme();" << d->currentThemeName << "to" << name; d->currentThemeName = name; if (d->themeMenuAction && d->themeMenuActionGroup) { QList list = d->themeMenuActionGroup->actions(); Q_FOREACH (QAction* action, list) { if (action->text().remove('&') == name) { action->setChecked(true); } } } slotChangePalette(); } void ThemeManager::slotChangePalette() { //qDebug() << "slotChangePalette" << sender(); QString theme(currentThemeName()); QString filename = d->themeMap.value(theme); KSharedConfigPtr config = KSharedConfig::openConfig(filename); QPalette palette = qApp->palette(); QPalette::ColorGroup states[3] = { QPalette::Active, QPalette::Inactive, QPalette::Disabled }; // TT thinks tooltips shouldn't use active, so we use our active colors for all states KColorScheme schemeTooltip(QPalette::Active, KColorScheme::Tooltip, config); for ( int i = 0; i < 3 ; ++i ) { QPalette::ColorGroup state = states[i]; KColorScheme schemeView(state, KColorScheme::View, config); KColorScheme schemeWindow(state, KColorScheme::Window, config); KColorScheme schemeButton(state, KColorScheme::Button, config); KColorScheme schemeSelection(state, KColorScheme::Selection, config); palette.setBrush(state, QPalette::WindowText, schemeWindow.foreground()); palette.setBrush(state, QPalette::Window, schemeWindow.background()); palette.setBrush(state, QPalette::Base, schemeView.background()); palette.setBrush(state, QPalette::Text, schemeView.foreground()); palette.setBrush(state, QPalette::Button, schemeButton.background()); palette.setBrush(state, QPalette::ButtonText, schemeButton.foreground()); palette.setBrush(state, QPalette::Highlight, schemeSelection.background()); palette.setBrush(state, QPalette::HighlightedText, schemeSelection.foreground()); palette.setBrush(state, QPalette::ToolTipBase, schemeTooltip.background()); palette.setBrush(state, QPalette::ToolTipText, schemeTooltip.foreground()); palette.setColor(state, QPalette::Light, schemeWindow.shade(KColorScheme::LightShade)); palette.setColor(state, QPalette::Midlight, schemeWindow.shade(KColorScheme::MidlightShade)); palette.setColor(state, QPalette::Mid, schemeWindow.shade(KColorScheme::MidShade)); palette.setColor(state, QPalette::Dark, schemeWindow.shade(KColorScheme::DarkShade)); palette.setColor(state, QPalette::Shadow, schemeWindow.shade(KColorScheme::ShadowShade)); palette.setBrush(state, QPalette::AlternateBase, schemeView.background(KColorScheme::AlternateBackground)); palette.setBrush(state, QPalette::Link, schemeView.foreground(KColorScheme::LinkText)); palette.setBrush(state, QPalette::LinkVisited, schemeView.foreground(KColorScheme::VisitedText)); } //qDebug() << ">>>>>>>>>>>>>>>>>> going to set palette on app" << theme; // hint for the style to synchronize the color scheme with the window manager/compositor qApp->setProperty("KDE_COLOR_SCHEME_PATH", filename); qApp->setPalette(palette); #ifdef Q_OS_MACOS if (theme == "Krita bright" || theme.isEmpty()) { qApp->setStyle("Macintosh"); qApp->style()->polish(qApp); } else { qApp->setStyle("Fusion"); qApp->style()->polish(qApp); } #endif KisIconUtils::clearIconCache(); emit signalThemeChanged(); } void ThemeManager::setThemeMenuAction(KActionMenu* const action) { d->themeMenuAction = action; populateThemeMenu(); } void ThemeManager::registerThemeActions(KActionCollection *actionCollection) { if (!d->themeMenuAction) return; actionCollection->addAction("theme_menu", d->themeMenuAction); } void ThemeManager::populateThemeMenu() { if (!d->themeMenuAction) return; d->themeMenuAction->menu()->clear(); delete d->themeMenuActionGroup; d->themeMenuActionGroup = new QActionGroup(d->themeMenuAction); connect(d->themeMenuActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotChangePalette())); QAction * action; const QStringList schemeFiles = KoResourcePaths::findAllResources("data", "color-schemes/*.colors"); QMap actionMap; for (int i = 0; i < schemeFiles.size(); ++i) { const QString filename = schemeFiles.at(i); const QFileInfo info(filename); KSharedConfigPtr config = KSharedConfig::openConfig(filename); QIcon icon = createSchemePreviewIcon(config); KConfigGroup group(config, "General"); const QString name = group.readEntry("Name", info.completeBaseName()); action = new QAction(name, d->themeMenuActionGroup); action->setIcon(icon); action->setCheckable(true); actionMap.insert(name, action); } // sort the list QStringList actionMapKeys = actionMap.keys(); actionMapKeys.sort(); Q_FOREACH (const QString& name, actionMapKeys) { if ( name == currentThemeName()) { actionMap.value(name)->setChecked(true); } d->themeMenuAction->addAction(actionMap.value(name)); } } QPixmap ThemeManager::createSchemePreviewIcon(const KSharedConfigPtr& config) { // code taken from kdebase/workspace/kcontrol/colors/colorscm.cpp const uchar bits1[] = { 0xff, 0xff, 0xff, 0x2c, 0x16, 0x0b }; const uchar bits2[] = { 0x68, 0x34, 0x1a, 0xff, 0xff, 0xff }; const QSize bitsSize(24, 2); const QBitmap b1 = QBitmap::fromData(bitsSize, bits1); const QBitmap b2 = QBitmap::fromData(bitsSize, bits2); QPixmap pixmap(23, 16); pixmap.fill(Qt::black); // FIXME use some color other than black for borders? KConfigGroup group(config, "WM"); QPainter p(&pixmap); KColorScheme windowScheme(QPalette::Active, KColorScheme::Window, config); p.fillRect(1, 1, 7, 7, windowScheme.background()); p.fillRect(2, 2, 5, 2, QBrush(windowScheme.foreground().color(), b1)); KColorScheme buttonScheme(QPalette::Active, KColorScheme::Button, config); p.fillRect(8, 1, 7, 7, buttonScheme.background()); p.fillRect(9, 2, 5, 2, QBrush(buttonScheme.foreground().color(), b1)); p.fillRect(15, 1, 7, 7, group.readEntry("activeBackground", QColor(96, 148, 207))); p.fillRect(16, 2, 5, 2, QBrush(group.readEntry("activeForeground", QColor(255, 255, 255)), b1)); KColorScheme viewScheme(QPalette::Active, KColorScheme::View, config); p.fillRect(1, 8, 7, 7, viewScheme.background()); p.fillRect(2, 12, 5, 2, QBrush(viewScheme.foreground().color(), b2)); KColorScheme selectionScheme(QPalette::Active, KColorScheme::Selection, config); p.fillRect(8, 8, 7, 7, selectionScheme.background()); p.fillRect(9, 12, 5, 2, QBrush(selectionScheme.foreground().color(), b2)); p.fillRect(15, 8, 7, 7, group.readEntry("inactiveBackground", QColor(224, 223, 222))); p.fillRect(16, 12, 5, 2, QBrush(group.readEntry("inactiveForeground", QColor(20, 19, 18)), b2)); p.end(); return pixmap; } void ThemeManager::populateThemeMap() { const QStringList schemeFiles = KoResourcePaths::findAllResources("data", "color-schemes/*.colors"); for (int i = 0; i < schemeFiles.size(); ++i) { const QString filename = schemeFiles.at(i); const QFileInfo info(filename); KSharedConfigPtr config = KSharedConfig::openConfig(filename); KConfigGroup group(config, "General"); const QString name = group.readEntry("Name", info.completeBaseName()); d->themeMap.insert(name, filename); } } } // namespace Digikam diff --git a/libs/ui/thememanager.h b/libs/ui/thememanager.h index 0e2dc818e8..338c518822 100644 --- a/libs/ui/thememanager.h +++ b/libs/ui/thememanager.h @@ -1,85 +1,85 @@ /* ============================================================ * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * Date : 2004-08-02 * Description : theme manager * * Copyright (C) 2006-2011 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef THEMEMANAGER_H #define THEMEMANAGER_H // Qt includes #include #include #include // KDE includes #include class KActionCollection; class KActionMenu; namespace Digikam { class ThemeManager : public QObject { Q_OBJECT public: /** * @brief ThemeManager * @param theme the currently active theme: the palette will not be changed to this theme * @param parent */ explicit ThemeManager(const QString &theme = "", QObject *parent = 0); ~ThemeManager() override; QString currentThemeName() const; void setCurrentTheme(const QString& name); void setThemeMenuAction(KActionMenu* const action); void registerThemeActions(KActionCollection *actionCollection); Q_SIGNALS: void signalThemeChanged(); private Q_SLOTS: void slotChangePalette(); private: void populateThemeMap(); void populateThemeMenu(); QPixmap createSchemePreviewIcon(const KSharedConfigPtr& config); private: class ThemeManagerPriv; ThemeManagerPriv* const d; }; } // namespace Digikam #endif /* THEMEMANAGER_H */ diff --git a/libs/ui/tool/kis_tool_freehand_helper.cpp b/libs/ui/tool/kis_tool_freehand_helper.cpp index d4665eeb04..fe2692c67e 100644 --- a/libs/ui/tool/kis_tool_freehand_helper.cpp +++ b/libs/ui/tool/kis_tool_freehand_helper.cpp @@ -1,964 +1,964 @@ /* * 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_tool_freehand_helper.h" #include #include #include #include #include #include "kis_algebra_2d.h" #include "kis_distance_information.h" #include "kis_painting_information_builder.h" #include "kis_image.h" #include "kis_painter.h" #include #include #include "kis_update_time_monitor.h" #include "kis_stabilized_events_sampler.h" #include "KisStabilizerDelayedPaintHelper.h" #include "kis_config.h" #include "kis_random_source.h" #include "KisPerStrokeRandomSource.h" #include "strokes/freehand_stroke.h" #include "strokes/KisFreehandStrokeInfo.h" #include "KisAsyncronousStrokeUpdateHelper.h" #include //#define DEBUG_BEZIER_CURVES // Factor by which to scale the airbrush timer's interval, relative to the actual airbrushing rate. // Setting this less than 1 makes the timer-generated pseudo-events happen faster than the desired // airbrush rate, which can improve responsiveness. const qreal AIRBRUSH_INTERVAL_FACTOR = 0.5; // The amount of time, in milliseconds, to allow between updates of the spacing information. Only // used when spacing updates between dabs are enabled. const qreal SPACING_UPDATE_INTERVAL = 50.0; // The amount of time, in milliseconds, to allow between updates of the timing information. Only // used when airbrushing. const qreal TIMING_UPDATE_INTERVAL = 50.0; struct KisToolFreehandHelper::Private { KisPaintingInformationBuilder *infoBuilder; KisStrokesFacade *strokesFacade; KisAsyncronousStrokeUpdateHelper asyncUpdateHelper; KUndo2MagicString transactionText; bool haveTangent; QPointF previousTangent; bool hasPaintAtLeastOnce; QTime strokeTime; QTimer strokeTimeoutTimer; QVector strokeInfos; KisResourcesSnapshotSP resources; KisStrokeId strokeId; KisPaintInformation previousPaintInformation; KisPaintInformation olderPaintInformation; KisSmoothingOptionsSP smoothingOptions; // fake random sources for hovering outline *only* KisRandomSourceSP fakeDabRandomSource; KisPerStrokeRandomSourceSP fakeStrokeRandomSource; // Timer used to generate paint updates periodically even without input events. This is only // used for paintops that depend on timely updates even when the cursor is not moving, e.g. for // airbrushing effects. QTimer airbrushingTimer; QList history; QList distanceHistory; // Keeps track of past cursor positions. This is used to determine the drawing angle when // drawing the brush outline or starting a stroke. KisPaintOpUtils::PositionHistory lastCursorPos; // Stabilizer data bool usingStabilizer; QQueue stabilizerDeque; QTimer stabilizerPollTimer; KisStabilizedEventsSampler stabilizedSampler; KisStabilizerDelayedPaintHelper stabilizerDelayedPaintHelper; qreal effectiveSmoothnessDistance() const; }; KisToolFreehandHelper::KisToolFreehandHelper(KisPaintingInformationBuilder *infoBuilder, const KUndo2MagicString &transactionText, KisSmoothingOptions *smoothingOptions) : m_d(new Private()) { m_d->infoBuilder = infoBuilder; m_d->transactionText = transactionText; m_d->smoothingOptions = KisSmoothingOptionsSP( smoothingOptions ? smoothingOptions : new KisSmoothingOptions()); m_d->fakeDabRandomSource = new KisRandomSource(); m_d->fakeStrokeRandomSource = new KisPerStrokeRandomSource(); m_d->strokeTimeoutTimer.setSingleShot(true); connect(&m_d->strokeTimeoutTimer, SIGNAL(timeout()), SLOT(finishStroke())); connect(&m_d->airbrushingTimer, SIGNAL(timeout()), SLOT(doAirbrushing())); connect(&m_d->stabilizerPollTimer, SIGNAL(timeout()), SLOT(stabilizerPollAndPaint())); connect(m_d->smoothingOptions.data(), SIGNAL(sigSmoothingTypeChanged()), SLOT(slotSmoothingTypeChanged())); m_d->stabilizerDelayedPaintHelper.setPaintLineCallback( [this](const KisPaintInformation &pi1, const KisPaintInformation &pi2) { paintLine(pi1, pi2); }); m_d->stabilizerDelayedPaintHelper.setUpdateOutlineCallback( [this]() { emit requestExplicitUpdateOutline(); }); } KisToolFreehandHelper::~KisToolFreehandHelper() { delete m_d; } void KisToolFreehandHelper::setSmoothness(KisSmoothingOptionsSP smoothingOptions) { m_d->smoothingOptions = smoothingOptions; } KisSmoothingOptionsSP KisToolFreehandHelper::smoothingOptions() const { return m_d->smoothingOptions; } QPainterPath KisToolFreehandHelper::paintOpOutline(const QPointF &savedCursorPos, const KoPointerEvent *event, const KisPaintOpSettingsSP globalSettings, KisPaintOpSettings::OutlineMode mode) const { KisPaintOpSettingsSP settings = globalSettings; KisPaintInformation info = m_d->infoBuilder->hover(savedCursorPos, event); QPointF prevPoint = m_d->lastCursorPos.pushThroughHistory(savedCursorPos); qreal startAngle = KisAlgebra2D::directionBetweenPoints(prevPoint, savedCursorPos, 0); KisDistanceInformation distanceInfo(prevPoint, startAngle); if (!m_d->strokeInfos.isEmpty()) { settings = m_d->resources->currentPaintOpPreset()->settings(); if (m_d->stabilizerDelayedPaintHelper.running() && m_d->stabilizerDelayedPaintHelper.hasLastPaintInformation()) { info = m_d->stabilizerDelayedPaintHelper.lastPaintInformation(); } else { info = m_d->previousPaintInformation; } /** * When LoD mode is active it may happen that the helper has * already started a stroke, but it painted noting, because * all the work is being calculated by the scaled-down LodN * stroke. So at first we try to fetch the data from the lodN * stroke ("buddy") and then check if there is at least * something has been painted with this distance information * object. */ KisDistanceInformation *buddyDistance = m_d->strokeInfos.first()->buddyDragDistance(); if (buddyDistance) { /** * Tiny hack alert: here we fetch the distance information * directly from the LodN stroke. Ideally, we should * upscale its data, but here we just override it with our * local copy of the coordinates. */ distanceInfo = *buddyDistance; distanceInfo.overrideLastValues(prevPoint, startAngle); } else if (m_d->strokeInfos.first()->dragDistance->isStarted()) { distanceInfo = *m_d->strokeInfos.first()->dragDistance; } } KisPaintInformation::DistanceInformationRegistrar registrar = info.registerDistanceInformation(&distanceInfo); info.setRandomSource(m_d->fakeDabRandomSource); info.setPerStrokeRandomSource(m_d->fakeStrokeRandomSource); QPainterPath outline = settings->brushOutline(info, mode); if (m_d->resources && m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER && m_d->smoothingOptions->useDelayDistance()) { const qreal R = m_d->smoothingOptions->delayDistance() / m_d->resources->effectiveZoom(); outline.addEllipse(info.pos(), R, R); } return outline; } void KisToolFreehandHelper::cursorMoved(const QPointF &cursorPos) { m_d->lastCursorPos.pushThroughHistory(cursorPos); } void KisToolFreehandHelper::initPaint(KoPointerEvent *event, const QPointF &pixelCoords, KoCanvasResourceProvider *resourceManager, KisImageWSP image, KisNodeSP currentNode, KisStrokesFacade *strokesFacade, KisNodeSP overrideNode, KisDefaultBoundsBaseSP bounds) { QPointF prevPoint = m_d->lastCursorPos.pushThroughHistory(pixelCoords); m_d->strokeTime.start(); KisPaintInformation pi = m_d->infoBuilder->startStroke(event, elapsedStrokeTime(), resourceManager); qreal startAngle = KisAlgebra2D::directionBetweenPoints(prevPoint, pixelCoords, 0.0); initPaintImpl(startAngle, pi, resourceManager, image, currentNode, strokesFacade, overrideNode, bounds); } bool KisToolFreehandHelper::isRunning() const { return m_d->strokeId; } void KisToolFreehandHelper::initPaintImpl(qreal startAngle, const KisPaintInformation &pi, KoCanvasResourceProvider *resourceManager, KisImageWSP image, KisNodeSP currentNode, KisStrokesFacade *strokesFacade, KisNodeSP overrideNode, KisDefaultBoundsBaseSP bounds) { m_d->strokesFacade = strokesFacade; m_d->haveTangent = false; m_d->previousTangent = QPointF(); m_d->hasPaintAtLeastOnce = false; m_d->previousPaintInformation = pi; m_d->resources = new KisResourcesSnapshot(image, currentNode, resourceManager, bounds); if(overrideNode) { m_d->resources->setCurrentNode(overrideNode); } const bool airbrushing = m_d->resources->needsAirbrushing(); const bool useSpacingUpdates = m_d->resources->needsSpacingUpdates(); KisDistanceInitInfo startDistInfo(m_d->previousPaintInformation.pos(), startAngle, useSpacingUpdates ? SPACING_UPDATE_INTERVAL : LONG_TIME, airbrushing ? TIMING_UPDATE_INTERVAL : LONG_TIME, 0); KisDistanceInformation startDist = startDistInfo.makeDistInfo(); createPainters(m_d->strokeInfos, startDist); KisStrokeStrategy *stroke = new FreehandStrokeStrategy(m_d->resources, m_d->strokeInfos, m_d->transactionText); m_d->strokeId = m_d->strokesFacade->startStroke(stroke); m_d->history.clear(); m_d->distanceHistory.clear(); if (airbrushing) { m_d->airbrushingTimer.setInterval(computeAirbrushTimerInterval()); m_d->airbrushingTimer.start(); } else if (m_d->resources->presetNeedsAsynchronousUpdates()) { m_d->asyncUpdateHelper.startUpdateStream(m_d->strokesFacade, m_d->strokeId); } if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) { stabilizerStart(m_d->previousPaintInformation); } // If airbrushing, paint an initial dab immediately. This is a workaround for an issue where // some paintops (Dyna, Particle, Sketch) might never initialize their spacing/timing // information until paintAt is called. if (airbrushing) { paintAt(pi); } } void KisToolFreehandHelper::paintBezierSegment(KisPaintInformation pi1, KisPaintInformation pi2, QPointF tangent1, QPointF tangent2) { if (tangent1.isNull() || tangent2.isNull()) return; const qreal maxSanePoint = 1e6; QPointF controlTarget1; QPointF controlTarget2; // Shows the direction in which control points go QPointF controlDirection1 = pi1.pos() + tangent1; QPointF controlDirection2 = pi2.pos() - tangent2; // Lines in the direction of the control points QLineF line1(pi1.pos(), controlDirection1); QLineF line2(pi2.pos(), controlDirection2); // Lines to check whether the control points lay on the opposite // side of the line QLineF line3(controlDirection1, controlDirection2); QLineF line4(pi1.pos(), pi2.pos()); QPointF intersection; if (line3.intersect(line4, &intersection) == QLineF::BoundedIntersection) { qreal controlLength = line4.length() / 2; line1.setLength(controlLength); line2.setLength(controlLength); controlTarget1 = line1.p2(); controlTarget2 = line2.p2(); } else { QLineF::IntersectType type = line1.intersect(line2, &intersection); if (type == QLineF::NoIntersection || intersection.manhattanLength() > maxSanePoint) { intersection = 0.5 * (pi1.pos() + pi2.pos()); // dbgKrita << "WARNING: there is no intersection point " // << "in the basic smoothing algorithms"; } controlTarget1 = intersection; controlTarget2 = intersection; } // shows how near to the controlTarget the value raises qreal coeff = 0.8; qreal velocity1 = QLineF(QPointF(), tangent1).length(); qreal velocity2 = QLineF(QPointF(), tangent2).length(); if (velocity1 == 0.0 || velocity2 == 0.0) { velocity1 = 1e-6; velocity2 = 1e-6; warnKrita << "WARNING: Basic Smoothing: Velocity is Zero! Please report a bug:" << ppVar(velocity1) << ppVar(velocity2); } qreal similarity = qMin(velocity1/velocity2, velocity2/velocity1); // the controls should not differ more than 50% similarity = qMax(similarity, qreal(0.5)); // when the controls are symmetric, their size should be smaller // to avoid corner-like curves coeff *= 1 - qMax(qreal(0.0), similarity - qreal(0.8)); Q_ASSERT(coeff > 0); QPointF control1; QPointF control2; if (velocity1 > velocity2) { control1 = pi1.pos() * (1.0 - coeff) + coeff * controlTarget1; coeff *= similarity; control2 = pi2.pos() * (1.0 - coeff) + coeff * controlTarget2; } else { control2 = pi2.pos() * (1.0 - coeff) + coeff * controlTarget2; coeff *= similarity; control1 = pi1.pos() * (1.0 - coeff) + coeff * controlTarget1; } paintBezierCurve(pi1, control1, control2, pi2); } qreal KisToolFreehandHelper::Private::effectiveSmoothnessDistance() const { const qreal effectiveSmoothnessDistance = !smoothingOptions->useScalableDistance() ? smoothingOptions->smoothnessDistance() : smoothingOptions->smoothnessDistance() / resources->effectiveZoom(); return effectiveSmoothnessDistance; } void KisToolFreehandHelper::paintEvent(KoPointerEvent *event) { KisPaintInformation info = m_d->infoBuilder->continueStroke(event, elapsedStrokeTime()); KisUpdateTimeMonitor::instance()->reportMouseMove(info.pos()); paint(info); } void KisToolFreehandHelper::paint(KisPaintInformation &info) { /** * Smooth the coordinates out using the history and the * distance. This is a heavily modified version of an algo used in * Gimp and described in https://bugs.kde.org/show_bug.cgi?id=281267 and - * http://www24.atwiki.jp/sigetch_2007/pages/17.html. The main + * https://w.atwiki.jp/sigetch_2007/pages/17.html. The main * differences are: * * 1) It uses 'distance' instead of 'velocity', since time * measurements are too unstable in realworld environment * * 2) There is no 'Quality' parameter, since the number of samples * is calculated automatically * * 3) 'Tail Aggressiveness' is used for controlling the end of the * stroke * * 4) The formila is a little bit different: 'Distance' parameter * stands for $3 \Sigma$ */ if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::WEIGHTED_SMOOTHING && m_d->smoothingOptions->smoothnessDistance() > 0.0) { { // initialize current distance QPointF prevPos; if (!m_d->history.isEmpty()) { const KisPaintInformation &prevPi = m_d->history.last(); prevPos = prevPi.pos(); } else { prevPos = m_d->previousPaintInformation.pos(); } qreal currentDistance = QVector2D(info.pos() - prevPos).length(); m_d->distanceHistory.append(currentDistance); } m_d->history.append(info); qreal x = 0.0; qreal y = 0.0; if (m_d->history.size() > 3) { const qreal sigma = m_d->effectiveSmoothnessDistance() / 3.0; // '3.0' for (3 * sigma) range qreal gaussianWeight = 1 / (sqrt(2 * M_PI) * sigma); qreal gaussianWeight2 = sigma * sigma; qreal distanceSum = 0.0; qreal scaleSum = 0.0; qreal pressure = 0.0; qreal baseRate = 0.0; Q_ASSERT(m_d->history.size() == m_d->distanceHistory.size()); for (int i = m_d->history.size() - 1; i >= 0; i--) { qreal rate = 0.0; const KisPaintInformation nextInfo = m_d->history.at(i); double distance = m_d->distanceHistory.at(i); Q_ASSERT(distance >= 0.0); qreal pressureGrad = 0.0; if (i < m_d->history.size() - 1) { pressureGrad = nextInfo.pressure() - m_d->history.at(i + 1).pressure(); const qreal tailAgressiveness = 40.0 * m_d->smoothingOptions->tailAggressiveness(); if (pressureGrad > 0.0 ) { pressureGrad *= tailAgressiveness * (1.0 - nextInfo.pressure()); distance += pressureGrad * 3.0 * sigma; // (3 * sigma) --- holds > 90% of the region } } if (gaussianWeight2 != 0.0) { distanceSum += distance; rate = gaussianWeight * exp(-distanceSum * distanceSum / (2 * gaussianWeight2)); } if (m_d->history.size() - i == 1) { baseRate = rate; } else if (baseRate / rate > 100) { break; } scaleSum += rate; x += rate * nextInfo.pos().x(); y += rate * nextInfo.pos().y(); if (m_d->smoothingOptions->smoothPressure()) { pressure += rate * nextInfo.pressure(); } } if (scaleSum != 0.0) { x /= scaleSum; y /= scaleSum; if (m_d->smoothingOptions->smoothPressure()) { pressure /= scaleSum; } } if ((x != 0.0 && y != 0.0) || (x == info.pos().x() && y == info.pos().y())) { info.setPos(QPointF(x, y)); if (m_d->smoothingOptions->smoothPressure()) { info.setPressure(pressure); } m_d->history.last() = info; } } } if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::SIMPLE_SMOOTHING || m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::WEIGHTED_SMOOTHING) { // Now paint between the coordinates, using the bezier curve interpolation if (!m_d->haveTangent) { m_d->haveTangent = true; m_d->previousTangent = (info.pos() - m_d->previousPaintInformation.pos()) / qMax(qreal(1.0), info.currentTime() - m_d->previousPaintInformation.currentTime()); } else { QPointF newTangent = (info.pos() - m_d->olderPaintInformation.pos()) / qMax(qreal(1.0), info.currentTime() - m_d->olderPaintInformation.currentTime()); if (newTangent.isNull() || m_d->previousTangent.isNull()) { paintLine(m_d->previousPaintInformation, info); } else { paintBezierSegment(m_d->olderPaintInformation, m_d->previousPaintInformation, m_d->previousTangent, newTangent); } m_d->previousTangent = newTangent; } m_d->olderPaintInformation = m_d->previousPaintInformation; // Enable stroke timeout only when not airbrushing. if (!m_d->airbrushingTimer.isActive()) { m_d->strokeTimeoutTimer.start(100); } } else if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::NO_SMOOTHING){ paintLine(m_d->previousPaintInformation, info); } if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) { m_d->stabilizedSampler.addEvent(info); if (m_d->stabilizerDelayedPaintHelper.running()) { // Paint here so we don't have to rely on the timer // This is just a tricky source for a relatively stable 7ms "timer" m_d->stabilizerDelayedPaintHelper.paintSome(); } } else { m_d->previousPaintInformation = info; } if(m_d->airbrushingTimer.isActive()) { m_d->airbrushingTimer.start(); } } void KisToolFreehandHelper::endPaint() { if (!m_d->hasPaintAtLeastOnce) { paintAt(m_d->previousPaintInformation); } else if (m_d->smoothingOptions->smoothingType() != KisSmoothingOptions::NO_SMOOTHING) { finishStroke(); } m_d->strokeTimeoutTimer.stop(); if(m_d->airbrushingTimer.isActive()) { m_d->airbrushingTimer.stop(); } if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) { stabilizerEnd(); } if (m_d->asyncUpdateHelper.isActive()) { m_d->asyncUpdateHelper.endUpdateStream(); } /** * There might be some timer events still pending, so * we should cancel them. Use this flag for the purpose. * Please note that we are not in MT here, so no mutex * is needed */ m_d->strokeInfos.clear(); m_d->strokesFacade->endStroke(m_d->strokeId); m_d->strokeId.clear(); } void KisToolFreehandHelper::cancelPaint() { if (!m_d->strokeId) return; m_d->strokeTimeoutTimer.stop(); if (m_d->airbrushingTimer.isActive()) { m_d->airbrushingTimer.stop(); } if (m_d->asyncUpdateHelper.isActive()) { m_d->asyncUpdateHelper.cancelUpdateStream(); } if (m_d->stabilizerPollTimer.isActive()) { m_d->stabilizerPollTimer.stop(); } if (m_d->stabilizerDelayedPaintHelper.running()) { m_d->stabilizerDelayedPaintHelper.cancel(); } // see a comment in endPaint() m_d->strokeInfos.clear(); m_d->strokesFacade->cancelStroke(m_d->strokeId); m_d->strokeId.clear(); } int KisToolFreehandHelper::elapsedStrokeTime() const { return m_d->strokeTime.elapsed(); } void KisToolFreehandHelper::stabilizerStart(KisPaintInformation firstPaintInfo) { m_d->usingStabilizer = true; // FIXME: Ugly hack, this is no a "distance" in any way int sampleSize = qRound(m_d->effectiveSmoothnessDistance()); sampleSize = qMax(3, sampleSize); // Fill the deque with the current value repeated until filling the sample m_d->stabilizerDeque.clear(); for (int i = sampleSize; i > 0; i--) { m_d->stabilizerDeque.enqueue(firstPaintInfo); } // Poll and draw regularly KisConfig cfg(true); int stabilizerSampleSize = cfg.stabilizerSampleSize(); m_d->stabilizerPollTimer.setInterval(stabilizerSampleSize); m_d->stabilizerPollTimer.start(); bool delayedPaintEnabled = cfg.stabilizerDelayedPaint(); if (delayedPaintEnabled) { m_d->stabilizerDelayedPaintHelper.start(firstPaintInfo); } m_d->stabilizedSampler.clear(); m_d->stabilizedSampler.addEvent(firstPaintInfo); } KisPaintInformation KisToolFreehandHelper::getStabilizedPaintInfo(const QQueue &queue, const KisPaintInformation &lastPaintInfo) { KisPaintInformation result(lastPaintInfo.pos(), lastPaintInfo.pressure(), lastPaintInfo.xTilt(), lastPaintInfo.yTilt(), lastPaintInfo.rotation(), lastPaintInfo.tangentialPressure(), lastPaintInfo.perspective(), elapsedStrokeTime(), lastPaintInfo.drawingSpeed()); if (queue.size() > 1) { QQueue::const_iterator it = queue.constBegin(); QQueue::const_iterator end = queue.constEnd(); /** * The first point is going to be overridden by lastPaintInfo, skip it. */ it++; int i = 2; if (m_d->smoothingOptions->stabilizeSensors()) { while (it != end) { qreal k = qreal(i - 1) / i; // coeff for uniform averaging result.KisPaintInformation::mixOtherWithoutTime(k, *it); it++; i++; } } else{ while (it != end) { qreal k = qreal(i - 1) / i; // coeff for uniform averaging result.KisPaintInformation::mixOtherOnlyPosition(k, *it); it++; i++; } } } return result; } void KisToolFreehandHelper::stabilizerPollAndPaint() { KisStabilizedEventsSampler::iterator it; KisStabilizedEventsSampler::iterator end; std::tie(it, end) = m_d->stabilizedSampler.range(); QVector delayedPaintTodoItems; for (; it != end; ++it) { KisPaintInformation sampledInfo = *it; bool canPaint = true; if (m_d->smoothingOptions->useDelayDistance()) { const qreal R = m_d->smoothingOptions->delayDistance() / m_d->resources->effectiveZoom(); QPointF diff = sampledInfo.pos() - m_d->previousPaintInformation.pos(); qreal dx = sqrt(pow2(diff.x()) + pow2(diff.y())); if (!(dx > R)) { if (m_d->resources->needsAirbrushing()) { sampledInfo.setPos(m_d->previousPaintInformation.pos()); } else { canPaint = false; } } } if (canPaint) { KisPaintInformation newInfo = getStabilizedPaintInfo(m_d->stabilizerDeque, sampledInfo); if (m_d->stabilizerDelayedPaintHelper.running()) { delayedPaintTodoItems.append(newInfo); } else { paintLine(m_d->previousPaintInformation, newInfo); } m_d->previousPaintInformation = newInfo; // Push the new entry through the queue m_d->stabilizerDeque.dequeue(); m_d->stabilizerDeque.enqueue(sampledInfo); } else if (m_d->stabilizerDeque.head().pos() != m_d->previousPaintInformation.pos()) { QQueue::iterator it = m_d->stabilizerDeque.begin(); QQueue::iterator end = m_d->stabilizerDeque.end(); while (it != end) { *it = m_d->previousPaintInformation; ++it; } } } m_d->stabilizedSampler.clear(); if (m_d->stabilizerDelayedPaintHelper.running()) { m_d->stabilizerDelayedPaintHelper.update(delayedPaintTodoItems); } else { emit requestExplicitUpdateOutline(); } } void KisToolFreehandHelper::stabilizerEnd() { // Stop the timer m_d->stabilizerPollTimer.stop(); // Finish the line if (m_d->smoothingOptions->finishStabilizedCurve()) { // Process all the existing events first stabilizerPollAndPaint(); // Draw the finish line with pending events and a time override m_d->stabilizedSampler.addFinishingEvent(m_d->stabilizerDeque.size()); stabilizerPollAndPaint(); } if (m_d->stabilizerDelayedPaintHelper.running()) { m_d->stabilizerDelayedPaintHelper.end(); } m_d->usingStabilizer = false; } void KisToolFreehandHelper::slotSmoothingTypeChanged() { if (!isRunning()) { return; } KisSmoothingOptions::SmoothingType currentSmoothingType = m_d->smoothingOptions->smoothingType(); if (m_d->usingStabilizer && (currentSmoothingType != KisSmoothingOptions::STABILIZER)) { stabilizerEnd(); } else if (!m_d->usingStabilizer && (currentSmoothingType == KisSmoothingOptions::STABILIZER)) { stabilizerStart(m_d->previousPaintInformation); } } void KisToolFreehandHelper::finishStroke() { if (m_d->haveTangent) { m_d->haveTangent = false; QPointF newTangent = (m_d->previousPaintInformation.pos() - m_d->olderPaintInformation.pos()) / (m_d->previousPaintInformation.currentTime() - m_d->olderPaintInformation.currentTime()); paintBezierSegment(m_d->olderPaintInformation, m_d->previousPaintInformation, m_d->previousTangent, newTangent); } } void KisToolFreehandHelper::doAirbrushing() { // Check that the stroke hasn't ended. if (!m_d->strokeInfos.isEmpty()) { // Add a new painting update at a point identical to the previous one, except for the time // and speed information. const KisPaintInformation &prevPaint = m_d->previousPaintInformation; KisPaintInformation nextPaint(prevPaint.pos(), prevPaint.pressure(), prevPaint.xTilt(), prevPaint.yTilt(), prevPaint.rotation(), prevPaint.tangentialPressure(), prevPaint.perspective(), elapsedStrokeTime(), 0.0); paint(nextPaint); } } int KisToolFreehandHelper::computeAirbrushTimerInterval() const { qreal realInterval = m_d->resources->airbrushingInterval() * AIRBRUSH_INTERVAL_FACTOR; return qMax(1, qFloor(realInterval)); } void KisToolFreehandHelper::paintAt(int strokeInfoId, const KisPaintInformation &pi) { m_d->hasPaintAtLeastOnce = true; m_d->strokesFacade->addJob(m_d->strokeId, new FreehandStrokeStrategy::Data(strokeInfoId, pi)); } void KisToolFreehandHelper::paintLine(int strokeInfoId, const KisPaintInformation &pi1, const KisPaintInformation &pi2) { m_d->hasPaintAtLeastOnce = true; m_d->strokesFacade->addJob(m_d->strokeId, new FreehandStrokeStrategy::Data(strokeInfoId, pi1, pi2)); } void KisToolFreehandHelper::paintBezierCurve(int strokeInfoId, const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2) { #ifdef DEBUG_BEZIER_CURVES KisPaintInformation tpi1; KisPaintInformation tpi2; tpi1 = pi1; tpi2 = pi2; tpi1.setPressure(0.3); tpi2.setPressure(0.3); paintLine(tpi1, tpi2); tpi1.setPressure(0.6); tpi2.setPressure(0.3); tpi1.setPos(pi1.pos()); tpi2.setPos(control1); paintLine(tpi1, tpi2); tpi1.setPos(pi2.pos()); tpi2.setPos(control2); paintLine(tpi1, tpi2); #endif m_d->hasPaintAtLeastOnce = true; m_d->strokesFacade->addJob(m_d->strokeId, new FreehandStrokeStrategy::Data(strokeInfoId, pi1, control1, control2, pi2)); } void KisToolFreehandHelper::createPainters(QVector &strokeInfos, const KisDistanceInformation &startDist) { strokeInfos << new KisFreehandStrokeInfo(startDist); } void KisToolFreehandHelper::paintAt(const KisPaintInformation &pi) { paintAt(0, pi); } void KisToolFreehandHelper::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2) { paintLine(0, pi1, pi2); } void KisToolFreehandHelper::paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2) { paintBezierCurve(0, pi1, control1, control2, pi2); } diff --git a/libs/ui/utils/KisClipboardUtil.cpp b/libs/ui/utils/KisClipboardUtil.cpp index 49bc209a08..9f1039cc14 100644 --- a/libs/ui/utils/KisClipboardUtil.cpp +++ b/libs/ui/utils/KisClipboardUtil.cpp @@ -1,76 +1,76 @@ /* * Copyright (c) 2019 Dmitrii Utkin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisClipboardUtil.h" #include #include #include #include #include #include #include namespace KisClipboardUtil { struct ClipboardImageFormat { QSet mimeTypes; QString format; }; QImage getImageFromClipboard() { static const QList supportedFormats = { {{"image/png"}, "PNG"}, {{"image/tiff"}, "TIFF"}, {{"image/bmp", "image/x-bmp", "image/x-MS-bmp", "image/x-win-bitmap"}, "BMP"}, {{"image/jpeg"}, "JPG"} }; QClipboard *clipboard = QApplication::clipboard(); QImage image; const QSet &clipboardMimeTypes = clipboard->mimeData()->formats().toSet(); Q_FOREACH (const ClipboardImageFormat &item, supportedFormats) { const QSet &intersection = item.mimeTypes & clipboardMimeTypes; if (intersection.isEmpty()) { continue; } const QString &format = *intersection.constBegin(); const QByteArray &imageData = clipboard->mimeData()->data(format); if (imageData.isEmpty()) { continue; } if (image.loadFromData(imageData, item.format.toLatin1())) { break; } } if (image.isNull()) { image = clipboard->image(); } return image; } -} \ No newline at end of file +} diff --git a/libs/ui/widgets/KoDualColorButton.cpp b/libs/ui/widgets/KoDualColorButton.cpp index 8158fae70b..78b785faf5 100644 --- a/libs/ui/widgets/KoDualColorButton.cpp +++ b/libs/ui/widgets/KoDualColorButton.cpp @@ -1,402 +1,402 @@ /* This file is part of the KDE libraries Copyright (C) 1999 Daniel M. Duley This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoDualColorButton.h" #include "KoColor.h" #include "KoColorDisplayRendererInterface.h" #include #include "dcolorarrow.xbm" #include "dcolorreset.xpm" #include #include "KisDlgInternalColorSelector.h" #include "kis_signals_blocker.h" #include #include #include #include #include #include class Q_DECL_HIDDEN KoDualColorButton::Private { public: Private(const KoColor &fgColor, const KoColor &bgColor, QWidget *_dialogParent, const KoColorDisplayRendererInterface *_displayRenderer) : dialogParent(_dialogParent) , dragFlag( false ) , miniCtlFlag( false ) , foregroundColor(fgColor) , backgroundColor(bgColor) , displayRenderer(_displayRenderer) { updateArrows(); resetPixmap = QPixmap( (const char **)dcolorreset_xpm ); popDialog = true; } void updateArrows() { arrowBitmap = QPixmap(12,12); arrowBitmap.fill(Qt::transparent); QPainter p(&arrowBitmap); - p.setPen(dialogParent->palette().window().color()); + p.setPen(dialogParent->palette().windowText().color()); // arrow pointing left p.drawLine(0, 3, 7, 3); p.drawLine(1, 2, 1, 4); p.drawLine(2, 1, 2, 5); p.drawLine(3, 0, 3, 6); // arrow pointing down p.drawLine(8, 4, 8, 11); p.drawLine(5, 8, 11, 8); p.drawLine(6, 9, 10, 9); p.drawLine(7, 10, 9, 10); } QWidget* dialogParent; QPixmap arrowBitmap; QPixmap resetPixmap; bool dragFlag, miniCtlFlag; KoColor foregroundColor; KoColor backgroundColor; KisDlgInternalColorSelector *colorSelectorDialog; QPoint dragPosition; Selection tmpSelection; bool popDialog; const KoColorDisplayRendererInterface *displayRenderer; void init(KoDualColorButton *q); }; void KoDualColorButton::Private::init(KoDualColorButton *q) { if ( q->sizeHint().isValid() ) q->setMinimumSize( q->sizeHint() ); q->setAcceptDrops( true ); QString caption = i18n("Select a Color"); KisDlgInternalColorSelector::Config config = KisDlgInternalColorSelector::Config(); config.modal = false; colorSelectorDialog = new KisDlgInternalColorSelector(q, foregroundColor, config, caption, displayRenderer); connect(colorSelectorDialog, SIGNAL(signalForegroundColorChosen(KoColor)), q, SLOT(slotSetForeGroundColorFromDialog(KoColor))); connect(q, SIGNAL(foregroundColorChanged(KoColor)), colorSelectorDialog, SLOT(slotColorUpdated(KoColor))); } KoDualColorButton::KoDualColorButton(const KoColor &foregroundColor, const KoColor &backgroundColor, QWidget *parent, QWidget* dialogParent ) : QWidget( parent ), d( new Private(foregroundColor, backgroundColor, dialogParent, KoDumbColorDisplayRenderer::instance()) ) { d->init(this); } KoDualColorButton::KoDualColorButton(const KoColor &foregroundColor, const KoColor &backgroundColor, const KoColorDisplayRendererInterface *displayRenderer, QWidget *parent, QWidget* dialogParent) : QWidget( parent ), d( new Private(foregroundColor, backgroundColor, dialogParent, displayRenderer) ) { d->init(this); } KoDualColorButton::~KoDualColorButton() { delete d; } KoColor KoDualColorButton::foregroundColor() const { return d->foregroundColor; } KoColor KoDualColorButton::backgroundColor() const { return d->backgroundColor; } bool KoDualColorButton::popDialog() const { return d->popDialog; } QSize KoDualColorButton::sizeHint() const { return QSize( 34, 34 ); } void KoDualColorButton::setForegroundColor(const KoColor &color) { d->foregroundColor = color; { /** * The internal color selector might emit the color of a different profile, so * we should break this cycling dependency somehow. */ KisSignalsBlocker b(d->colorSelectorDialog); d->colorSelectorDialog->slotColorUpdated(color); } repaint(); } void KoDualColorButton::setBackgroundColor( const KoColor &color ) { d->backgroundColor = color; repaint(); } void KoDualColorButton::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer) { if (displayRenderer) { d->displayRenderer = displayRenderer; d->colorSelectorDialog->setDisplayRenderer(displayRenderer); connect(d->displayRenderer, SIGNAL(destroyed()), this, SLOT(setDisplayRenderer()), Qt::UniqueConnection); } else { d->displayRenderer = KoDumbColorDisplayRenderer::instance(); } } void KoDualColorButton::setColorSpace(const KoColorSpace *cs) { d->colorSelectorDialog->lockUsedColorSpace(cs); } QColor KoDualColorButton::getColorFromDisplayRenderer(KoColor c) { QColor col; if (d->displayRenderer) { c.convertTo(d->displayRenderer->getPaintingColorSpace()); col = d->displayRenderer->toQColor(c); } else { col = c.toQColor(); } return col; } void KoDualColorButton::setPopDialog( bool popDialog ) { d->popDialog = popDialog; } void KoDualColorButton::metrics( QRect &foregroundRect, QRect &backgroundRect ) { foregroundRect = QRect( 0, 0, width() - 14, height() - 14 ); backgroundRect = QRect( 14, 14, width() - 14, height() - 14 ); } void KoDualColorButton::paintEvent(QPaintEvent *) { QRect foregroundRect; QRect backgroundRect; QPainter painter( this ); metrics( foregroundRect, backgroundRect ); QBrush defBrush = palette().brush( QPalette::Button ); QBrush foregroundBrush( getColorFromDisplayRenderer(d->foregroundColor), Qt::SolidPattern ); QBrush backgroundBrush( getColorFromDisplayRenderer(d->backgroundColor), Qt::SolidPattern ); qDrawShadeRect( &painter, backgroundRect, palette(), false, 1, 0, isEnabled() ? &backgroundBrush : &defBrush ); qDrawShadeRect( &painter, foregroundRect, palette(), false, 1, 0, isEnabled() ? &foregroundBrush : &defBrush ); painter.setPen( palette().color( QPalette::Shadow ) ); painter.drawPixmap( foregroundRect.right() + 2, 1, d->arrowBitmap ); painter.drawPixmap( 1, foregroundRect.bottom() + 2, d->resetPixmap ); } void KoDualColorButton::dragEnterEvent( QDragEnterEvent *event ) { event->setAccepted( isEnabled() && KColorMimeData::canDecode( event->mimeData() ) ); } void KoDualColorButton::dropEvent( QDropEvent *event ) { Q_UNUSED(event); /* QColor color = KColorMimeData::fromMimeData( event->mimeData() ); if ( color.isValid() ) { if ( d->selection == Foreground ) { d->foregroundColor = color; emit foregroundColorChanged( color ); } else { d->backgroundColor = color; emit backgroundColorChanged( color ); } repaint(); } */ } void KoDualColorButton::slotSetForeGroundColorFromDialog(const KoColor color) { d->foregroundColor = color; repaint(); emit foregroundColorChanged(d->foregroundColor); } void KoDualColorButton::mousePressEvent( QMouseEvent *event ) { QRect foregroundRect; QRect backgroundRect; metrics( foregroundRect, backgroundRect ); d->dragPosition = event->pos(); d->dragFlag = false; if ( foregroundRect.contains( d->dragPosition ) ) { d->tmpSelection = Foreground; d->miniCtlFlag = false; } else if( backgroundRect.contains( d->dragPosition ) ) { d->tmpSelection = Background; d->miniCtlFlag = false; } else if ( event->pos().x() > foregroundRect.width() ) { // We handle the swap and reset controls as soon as the mouse is // is pressed and ignore further events on this click (mosfet). KoColor tmp = d->foregroundColor; d->foregroundColor = d->backgroundColor; d->backgroundColor = tmp; emit backgroundColorChanged( d->backgroundColor ); emit foregroundColorChanged( d->foregroundColor ); d->miniCtlFlag = true; } else if ( event->pos().x() < backgroundRect.x() ) { d->foregroundColor = d->displayRenderer->approximateFromRenderedQColor(Qt::black); d->backgroundColor = d->displayRenderer->approximateFromRenderedQColor(Qt::white); emit backgroundColorChanged( d->backgroundColor ); emit foregroundColorChanged( d->foregroundColor ); d->miniCtlFlag = true; } repaint(); } void KoDualColorButton::mouseMoveEvent( QMouseEvent *event ) { if ( !d->miniCtlFlag ) { int delay = QApplication::startDragDistance(); if ( event->x() >= d->dragPosition.x() + delay || event->x() <= d->dragPosition.x() - delay || event->y() >= d->dragPosition.y() + delay || event->y() <= d->dragPosition.y() - delay ) { KColorMimeData::createDrag( d->tmpSelection == Foreground ? getColorFromDisplayRenderer(d->foregroundColor) : getColorFromDisplayRenderer(d->backgroundColor), this )->exec(); d->dragFlag = true; } } } void KoDualColorButton::mouseReleaseEvent( QMouseEvent *event ) { d->dragFlag = false; if ( d->miniCtlFlag ) return; d->miniCtlFlag = false; QRect foregroundRect; QRect backgroundRect; metrics( foregroundRect, backgroundRect ); if (foregroundRect.contains( event->pos())) { if (d->tmpSelection == Foreground) { if (d->popDialog) { #ifndef Q_OS_MACOS d->colorSelectorDialog->setPreviousColor(d->foregroundColor); // this should toggle, but I don't know how to implement that... d->colorSelectorDialog->show(); #else QColor c = d->foregroundColor.toQColor(); c = QColorDialog::getColor(c, this); if (c.isValid()) { d->foregroundColor = d->displayRenderer->approximateFromRenderedQColor(c); emit foregroundColorChanged(d->foregroundColor); } #endif } } else { d->foregroundColor = d->backgroundColor; emit foregroundColorChanged( d->foregroundColor ); } } else if ( backgroundRect.contains( event->pos() )) { if(d->tmpSelection == Background ) { if( d->popDialog) { #ifndef Q_OS_MACOS KoColor c = d->backgroundColor; c = KisDlgInternalColorSelector::getModalColorDialog(c, this, d->colorSelectorDialog->windowTitle()); d->backgroundColor = c; emit backgroundColorChanged(d->backgroundColor); #else QColor c = d->backgroundColor.toQColor(); c = QColorDialog::getColor(c, this); if (c.isValid()) { d->backgroundColor = d->displayRenderer->approximateFromRenderedQColor(c); emit backgroundColorChanged(d->backgroundColor); } #endif } } else { d->backgroundColor = d->foregroundColor; emit backgroundColorChanged( d->backgroundColor ); } } repaint(); } void KoDualColorButton::changeEvent(QEvent *event) { QWidget::changeEvent(event); switch (event->type()) { case QEvent::StyleChange: case QEvent::PaletteChange: d->updateArrows(); default: break; } } diff --git a/libs/ui/widgets/KoFillConfigWidget.cpp b/libs/ui/widgets/KoFillConfigWidget.cpp index 603a748854..09be5326f2 100644 --- a/libs/ui/widgets/KoFillConfigWidget.cpp +++ b/libs/ui/widgets/KoFillConfigWidget.cpp @@ -1,926 +1,923 @@ /* This file is part of the KDE project * Made by Tomislav Lukman (tomislav.lukman@ck.tel.hr) * Copyright (C) 2012 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 "KoFillConfigWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoResourceServerProvider.h" #include "KoResourceServerAdapter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoZoomHandler.h" #include "KoColorPopupButton.h" #include "ui_KoFillConfigWidget.h" #include #include #include #include #include "kis_canvas_resource_provider.h" #include #include #include #include "kis_global.h" #include "kis_debug.h" static const char* const buttonnone[]={ "16 16 3 1", "# c #000000", "e c #ff0000", "- c #ffffff", "################", "#--------------#", "#-e----------e-#", "#--e--------e--#", "#---e------e---#", "#----e----e----#", "#-----e--e-----#", "#------ee------#", "#------ee------#", "#-----e--e-----#", "#----e----e----#", "#---e------e---#", "#--e--------e--#", "#-e----------e-#", "#--------------#", "################"}; static const char* const buttonsolid[]={ "16 16 2 1", "# c #000000", ". c #969696", "################", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "################"}; // FIXME: Smoother gradient button. static const char* const buttongradient[]={ "16 16 15 1", "# c #000000", "n c #101010", "m c #202020", "l c #303030", "k c #404040", "j c #505050", "i c #606060", "h c #707070", "g c #808080", "f c #909090", "e c #a0a0a0", "d c #b0b0b0", "c c #c0c0c0", "b c #d0d0d0", "a c #e0e0e0", "################", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "################"}; static const char* const buttonpattern[]={ "16 16 4 1", ". c #0a0a0a", "# c #333333", "a c #a0a0a0", "b c #ffffffff", "################", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#bbbbbaaaabbbbb#", "#bbbbbaaaabbbbb#", "#bbbbbaaaabbbbb#", "#bbbbbaaaabbbbb#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "################"}; class Q_DECL_HIDDEN KoFillConfigWidget::Private { public: Private(KoFlake::FillVariant _fillVariant) : canvas(0), colorChangedCompressor(100, KisSignalCompressor::FIRST_ACTIVE), gradientChangedCompressor(100, KisSignalCompressor::FIRST_ACTIVE), shapeChangedCompressor(200,KisSignalCompressor::FIRST_ACTIVE), fillVariant(_fillVariant), noSelectionTrackingMode(false) { } KoColorPopupAction *colorAction; KoResourcePopupAction *gradientAction; KoResourcePopupAction *patternAction; QButtonGroup *group; KoCanvasBase *canvas; KisSignalCompressor colorChangedCompressor; KisAcyclicSignalConnector shapeChangedAcyclicConnector; KisAcyclicSignalConnector resourceManagerAcyclicConnector; KoFillConfigWidget::StyleButton selectedFillIndex {KoFillConfigWidget::None}; QSharedPointer activeGradient; KisSignalCompressor gradientChangedCompressor; KisSignalCompressor shapeChangedCompressor; KoFlake::FillVariant fillVariant; bool noSelectionTrackingMode; Ui_KoFillConfigWidget *ui; std::vector deactivationLocks; boost::optional overriddenColorFromProvider; }; KoFillConfigWidget::KoFillConfigWidget(KoCanvasBase *canvas, KoFlake::FillVariant fillVariant, bool trackShapeSelection, QWidget *parent) : QWidget(parent) , d(new Private(fillVariant)) { d->canvas = canvas; if (trackShapeSelection) { d->shapeChangedAcyclicConnector.connectBackwardVoid( d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), &d->shapeChangedCompressor, SLOT(start())); d->shapeChangedAcyclicConnector.connectBackwardVoid( d->canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()), &d->shapeChangedCompressor, SLOT(start())); connect(&d->shapeChangedCompressor, SIGNAL(timeout()), this, SLOT(shapeChanged())); } d->resourceManagerAcyclicConnector.connectBackwardResourcePair( d->canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), this, SLOT(slotCanvasResourceChanged(int,QVariant))); d->resourceManagerAcyclicConnector.connectForwardVoid( this, SIGNAL(sigInternalRequestColorToResourceManager()), this, SLOT(slotProposeCurrentColorToResourceManager())); KisAcyclicSignalConnector *resetConnector = d->resourceManagerAcyclicConnector.createCoordinatedConnector(); resetConnector->connectForwardVoid( this, SIGNAL(sigInternalRecoverColorInResourceManager()), this, SLOT(slotRecoverColorInResourceManager())); // configure GUI d->ui = new Ui_KoFillConfigWidget(); d->ui->setupUi(this); d->group = new QButtonGroup(this); d->group->setExclusive(true); d->ui->btnNoFill->setIcon(QPixmap((const char **) buttonnone)); d->group->addButton(d->ui->btnNoFill, None); d->ui->btnSolidFill->setIcon(QPixmap((const char **) buttonsolid)); d->group->addButton(d->ui->btnSolidFill, Solid); d->ui->btnGradientFill->setIcon(QPixmap((const char **) buttongradient)); d->group->addButton(d->ui->btnGradientFill, Gradient); d->ui->btnPatternFill->setIcon(QPixmap((const char **) buttonpattern)); d->group->addButton(d->ui->btnPatternFill, Pattern); d->ui->btnPatternFill->setVisible(false); d->colorAction = new KoColorPopupAction(d->ui->btnChooseSolidColor); d->colorAction->setToolTip(i18n("Change the filling color")); d->colorAction->setCurrentColor(Qt::white); d->ui->btnChooseSolidColor->setDefaultAction(d->colorAction); d->ui->btnChooseSolidColor->setPopupMode(QToolButton::InstantPopup); d->ui->btnSolidColorPick->setIcon(KisIconUtils::loadIcon("krita_tool_color_picker")); // TODO: for now the color picking button is disabled! d->ui->btnSolidColorPick->setEnabled(false); d->ui->btnSolidColorPick->setVisible(false); connect(d->colorAction, SIGNAL(colorChanged(KoColor)), &d->colorChangedCompressor, SLOT(start())); connect(&d->colorChangedCompressor, SIGNAL(timeout()), SLOT(colorChanged())); connect(d->ui->btnChooseSolidColor, SIGNAL(iconSizeChanged()), d->colorAction, SLOT(updateIcon())); connect(d->group, SIGNAL(buttonClicked(int)), SLOT(styleButtonPressed(int))); connect(d->group, SIGNAL(buttonClicked(int)), SLOT(slotUpdateFillTitle())); slotUpdateFillTitle(); styleButtonPressed(d->group->checkedId()); // Gradient selector d->ui->wdgGradientEditor->setCompactMode(true); connect(d->ui->wdgGradientEditor, SIGNAL(sigGradientChanged()), &d->gradientChangedCompressor, SLOT(start())); connect(&d->gradientChangedCompressor, SIGNAL(timeout()), SLOT(activeGradientChanged())); KoResourceServerProvider *serverProvider = KoResourceServerProvider::instance(); QSharedPointer gradientResourceAdapter( new KoResourceServerAdapter(serverProvider->gradientServer())); d->gradientAction = new KoResourcePopupAction(gradientResourceAdapter, d->ui->btnChoosePredefinedGradient); d->gradientAction->setToolTip(i18n("Change filling gradient")); d->ui->btnChoosePredefinedGradient->setDefaultAction(d->gradientAction); d->ui->btnChoosePredefinedGradient->setPopupMode(QToolButton::InstantPopup); connect(d->gradientAction, SIGNAL(resourceSelected(QSharedPointer)), SLOT(gradientResourceChanged())); connect(d->ui->btnChoosePredefinedGradient, SIGNAL(iconSizeChanged()), d->gradientAction, SLOT(updateIcon())); d->ui->btnSaveGradient->setIcon(KisIconUtils::loadIcon("document-save")); connect(d->ui->btnSaveGradient, SIGNAL(clicked()), SLOT(slotSavePredefinedGradientClicked())); connect(d->ui->cmbGradientRepeat, SIGNAL(currentIndexChanged(int)), SLOT(slotGradientRepeatChanged())); connect(d->ui->cmbGradientType, SIGNAL(currentIndexChanged(int)), SLOT(slotGradientTypeChanged())); deactivate(); #if 0 // Pattern selector QSharedPointerpatternResourceAdapter(new KoResourceServerAdapter(serverProvider->patternServer())); d->patternAction = new KoResourcePopupAction(patternResourceAdapter, d->colorButton); d->patternAction->setToolTip(i18n("Change the filling pattern")); connect(d->patternAction, SIGNAL(resourceSelected(QSharedPointer)), this, SLOT(patternChanged(QSharedPointer))); connect(d->colorButton, SIGNAL(iconSizeChanged()), d->patternAction, SLOT(updateIcon())); #endif } KoFillConfigWidget::~KoFillConfigWidget() { delete d; } void KoFillConfigWidget::activate() { - KIS_SAFE_ASSERT_RECOVER_RETURN(!d->deactivationLocks.empty()); d->deactivationLocks.clear(); - if (!d->noSelectionTrackingMode) { d->shapeChangedCompressor.start(); } else { loadCurrentFillFromResourceServer(); } updateWidgetComponentVisbility(); } void KoFillConfigWidget::deactivate() { emit sigInternalRecoverColorInResourceManager(); - - KIS_SAFE_ASSERT_RECOVER_RETURN(d->deactivationLocks.empty()); + d->deactivationLocks.clear(); d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->shapeChangedAcyclicConnector)); d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->resourceManagerAcyclicConnector)); } void KoFillConfigWidget::forceUpdateOnSelectionChanged() { d->shapeChangedCompressor.start(); } void KoFillConfigWidget::setNoSelectionTrackingMode(bool value) { d->noSelectionTrackingMode = value; if (!d->noSelectionTrackingMode) { d->shapeChangedCompressor.start(); } } void KoFillConfigWidget::slotUpdateFillTitle() { QString text = d->group->checkedButton() ? d->group->checkedButton()->text() : QString(); text.replace('&', QString()); d->ui->lblFillTitle->setText(text); } void KoFillConfigWidget::slotCanvasResourceChanged(int key, const QVariant &value) { if ((key == KoCanvasResourceProvider::ForegroundColor && d->fillVariant == KoFlake::Fill) || (key == KoCanvasResourceProvider::BackgroundColor && d->fillVariant == KoFlake::StrokeFill && !d->noSelectionTrackingMode) || (key == KoCanvasResourceProvider::ForegroundColor && d->noSelectionTrackingMode)) { KoColor color = value.value(); const int checkedId = d->group->checkedId(); if ((checkedId < 0 || checkedId == None || checkedId == Solid) && !(checkedId == Solid && d->colorAction->currentKoColor() == color)) { d->group->button(Solid)->setChecked(true); d->selectedFillIndex = Solid; d->colorAction->setCurrentColor(color); d->colorChangedCompressor.start(); } else if (checkedId == Gradient && key == KoCanvasResourceProvider::ForegroundColor) { d->ui->wdgGradientEditor->notifyGlobalColorChanged(color); } } else if (key == KisCanvasResourceProvider::CurrentGradient) { KoResource *gradient = value.value(); const int checkedId = d->group->checkedId(); if (gradient && (checkedId < 0 || checkedId == None || checkedId == Gradient)) { d->group->button(Gradient)->setChecked(true); d->gradientAction->setCurrentResource(gradient); } } } QList KoFillConfigWidget::currentShapes() { return d->canvas->selectedShapesProxy()->selection()->selectedEditableShapes(); } int KoFillConfigWidget::selectedFillIndex() { return d->selectedFillIndex; } void KoFillConfigWidget::styleButtonPressed(int buttonId) { QList shapes = currentShapes(); switch (buttonId) { case KoFillConfigWidget::None: noColorSelected(); break; case KoFillConfigWidget::Solid: colorChanged(); break; case KoFillConfigWidget::Gradient: if (d->activeGradient) { setNewGradientBackgroundToShape(); updateGradientSaveButtonAvailability(); } else { gradientResourceChanged(); } break; case KoFillConfigWidget::Pattern: // Only select mode in the widget, don't set actual pattern :/ //d->colorButton->setDefaultAction(d->patternAction); //patternChanged(d->patternAction->currentBackground()); break; } // update tool option fields with first selected object if (shapes.isEmpty() == false) { KoShape *firstShape = shapes.first(); updateFillIndexFromShape(firstShape); updateFillColorFromShape(firstShape); } updateWidgetComponentVisbility(); } KoShapeStrokeSP KoFillConfigWidget::createShapeStroke() { KoShapeStrokeSP stroke(new KoShapeStroke()); KIS_ASSERT_RECOVER_RETURN_VALUE(d->fillVariant == KoFlake::StrokeFill, stroke); switch (d->group->checkedId()) { case KoFillConfigWidget::None: stroke->setColor(Qt::transparent); break; case KoFillConfigWidget::Solid: stroke->setColor(d->colorAction->currentColor()); break; case KoFillConfigWidget::Gradient: { QScopedPointer g(d->activeGradient->toQGradient()); QBrush newBrush = *g; stroke->setLineBrush(newBrush); stroke->setColor(Qt::transparent); break; } case KoFillConfigWidget::Pattern: break; } return stroke; } void KoFillConfigWidget::noColorSelected() { KisAcyclicSignalConnector::Blocker b(d->shapeChangedAcyclicConnector); QList selectedShapes = currentShapes(); if (selectedShapes.isEmpty()) { emit sigFillChanged(); return; } KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant); KUndo2Command *command = wrapper.setColor(QColor()); if (command) { d->canvas->addCommand(command); } if (d->fillVariant == KoFlake::StrokeFill) { KUndo2Command *lineCommand = wrapper.setLineWidth(0.0); if (lineCommand) { d->canvas->addCommand(lineCommand); } } emit sigFillChanged(); } void KoFillConfigWidget::colorChanged() { KisAcyclicSignalConnector::Blocker b(d->shapeChangedAcyclicConnector); QList selectedShapes = currentShapes(); if (selectedShapes.isEmpty()) { emit sigInternalRequestColorToResourceManager(); emit sigFillChanged(); return; } d->overriddenColorFromProvider = boost::none; KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant); KUndo2Command *command = wrapper.setColor(d->colorAction->currentColor()); if (command) { d->canvas->addCommand(command); } // only returns true if it is a stroke object that has a 0 for line width if (wrapper.hasZeroLineWidth() ) { KUndo2Command *lineCommand = wrapper.setLineWidth(1.0); if (lineCommand) { d->canvas->addCommand(lineCommand); } // * line to test out QColor solidColor = d->colorAction->currentColor(); solidColor.setAlpha(255); command = wrapper.setColor(solidColor); if (command) { d->canvas->addCommand(command); } } d->colorAction->setCurrentColor(wrapper.color()); emit sigFillChanged(); emit sigInternalRequestColorToResourceManager(); } void KoFillConfigWidget::slotProposeCurrentColorToResourceManager() { const int checkedId = d->group->checkedId(); bool hasColor = false; KoColor color; KoCanvasResourceProvider::CanvasResource colorSlot = KoCanvasResourceProvider::ForegroundColor; if (checkedId == Solid) { if (d->fillVariant == KoFlake::StrokeFill) { colorSlot = KoCanvasResourceProvider::BackgroundColor; } color = d->colorAction->currentKoColor(); hasColor = true; } else if (checkedId == Gradient) { if (boost::optional gradientColor = d->ui->wdgGradientEditor->currentActiveStopColor()) { color = *gradientColor; hasColor = true; } } if (hasColor) { if (!d->overriddenColorFromProvider) { d->overriddenColorFromProvider = d->canvas->resourceManager()->resource(colorSlot).value(); } /** * Don't let opacity leak to our resource manager system * * NOTE: theoretically, we could guarantee it on a level of the * resource manager itself, */ color.setOpacity(OPACITY_OPAQUE_U8); d->canvas->resourceManager()->setResource(colorSlot, QVariant::fromValue(color)); } } void KoFillConfigWidget::slotRecoverColorInResourceManager() { if (d->overriddenColorFromProvider) { KoCanvasResourceProvider::CanvasResource colorSlot = KoCanvasResourceProvider::ForegroundColor; if (d->fillVariant == KoFlake::StrokeFill) { colorSlot = KoCanvasResourceProvider::BackgroundColor; } d->canvas->resourceManager()->setResource(colorSlot, QVariant::fromValue(*d->overriddenColorFromProvider)); d->overriddenColorFromProvider = boost::none; } } template QString findFirstAvailableResourceName(const QString &baseName, ResourceServer *server) { if (!server->resourceByName(baseName)) return baseName; int counter = 1; QString result; while ((result = QString("%1%2").arg(baseName).arg(counter)), server->resourceByName(result)) { counter++; } return result; } void KoFillConfigWidget::slotSavePredefinedGradientClicked() { KoResourceServerProvider *serverProvider = KoResourceServerProvider::instance(); auto server = serverProvider->gradientServer(); const QString defaultGradientNamePrefix = i18nc("default prefix for the saved gradient", "gradient"); QString name = d->activeGradient->name().isEmpty() ? defaultGradientNamePrefix : d->activeGradient->name(); name = findFirstAvailableResourceName(name, server); name = QInputDialog::getText(this, i18nc("@title:window", "Save Gradient"), i18n("Enter gradient name:"), QLineEdit::Normal, name); // TODO: currently we do not allow the user to // create two resources with the same name! // Please add some feedback for it! name = findFirstAvailableResourceName(name, server); d->activeGradient->setName(name); const QString saveLocation = server->saveLocation(); d->activeGradient->setFilename(saveLocation + d->activeGradient->name() + d->activeGradient->defaultFileExtension()); KoAbstractGradient *newGradient = d->activeGradient->clone(); server->addResource(newGradient); d->gradientAction->setCurrentResource(newGradient); } void KoFillConfigWidget::activeGradientChanged() { setNewGradientBackgroundToShape(); updateGradientSaveButtonAvailability(); emit sigInternalRequestColorToResourceManager(); } void KoFillConfigWidget::gradientResourceChanged() { QSharedPointer bg = qSharedPointerDynamicCast( d->gradientAction->currentBackground()); uploadNewGradientBackground(bg->gradient()); setNewGradientBackgroundToShape(); updateGradientSaveButtonAvailability(); } void KoFillConfigWidget::slotGradientTypeChanged() { QGradient::Type type = d->ui->cmbGradientType->currentIndex() == 0 ? QGradient::LinearGradient : QGradient::RadialGradient; d->activeGradient->setType(type); activeGradientChanged(); } void KoFillConfigWidget::slotGradientRepeatChanged() { QGradient::Spread spread = QGradient::Spread(d->ui->cmbGradientRepeat->currentIndex()); d->activeGradient->setSpread(spread); activeGradientChanged(); } void KoFillConfigWidget::uploadNewGradientBackground(const QGradient *gradient) { KisSignalsBlocker b1(d->ui->wdgGradientEditor, d->ui->cmbGradientType, d->ui->cmbGradientRepeat); d->ui->wdgGradientEditor->setGradient(0); d->activeGradient.reset(KoStopGradient::fromQGradient(gradient)); d->ui->wdgGradientEditor->setGradient(d->activeGradient.data()); d->ui->cmbGradientType->setCurrentIndex(d->activeGradient->type() != QGradient::LinearGradient); d->ui->cmbGradientRepeat->setCurrentIndex(int(d->activeGradient->spread())); } void KoFillConfigWidget::setNewGradientBackgroundToShape() { QList selectedShapes = currentShapes(); if (selectedShapes.isEmpty()) { emit sigFillChanged(); return; } KisAcyclicSignalConnector::Blocker b(d->shapeChangedAcyclicConnector); KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant); QScopedPointer srcQGradient(d->activeGradient->toQGradient()); KUndo2Command *command = wrapper.applyGradientStopsOnly(srcQGradient.data()); if (command) { d->canvas->addCommand(command); } emit sigFillChanged(); } void KoFillConfigWidget::updateGradientSaveButtonAvailability() { bool savingEnabled = false; QScopedPointer currentGradient(d->activeGradient->toQGradient()); QSharedPointer bg = d->gradientAction->currentBackground(); if (bg) { QSharedPointer resourceBackground = qSharedPointerDynamicCast(bg); savingEnabled = resourceBackground->gradient()->stops() != currentGradient->stops(); savingEnabled |= resourceBackground->gradient()->type() != currentGradient->type(); savingEnabled |= resourceBackground->gradient()->spread() != currentGradient->spread(); } d->ui->btnSaveGradient->setEnabled(savingEnabled); } void KoFillConfigWidget::patternChanged(QSharedPointer background) { Q_UNUSED(background); #if 0 QSharedPointer patternBackground = qSharedPointerDynamicCast(background); if (! patternBackground) { return; } QList selectedShapes = currentShapes(); if (selectedShapes.isEmpty()) { return; } KoImageCollection *imageCollection = d->canvas->shapeController()->resourceManager()->imageCollection(); if (imageCollection) { QSharedPointer fill(new KoPatternBackground(imageCollection)); fill->setPattern(patternBackground->pattern()); d->canvas->addCommand(new KoShapeBackgroundCommand(selectedShapes, fill)); } #endif } void KoFillConfigWidget::loadCurrentFillFromResourceServer() { { KoColor color = d->canvas->resourceManager()->backgroundColor(); slotCanvasResourceChanged(KoCanvasResourceProvider::BackgroundColor, QVariant::fromValue(color)); } { KoColor color = d->canvas->resourceManager()->foregroundColor(); slotCanvasResourceChanged(KoCanvasResourceProvider::ForegroundColor, QVariant::fromValue(color)); } Q_FOREACH (QAbstractButton *button, d->group->buttons()) { button->setEnabled(true); } emit sigFillChanged(); } void KoFillConfigWidget::shapeChanged() { if (d->noSelectionTrackingMode) return; QList shapes = currentShapes(); bool shouldUploadColorToResourceManager = false; if (shapes.isEmpty() || (shapes.size() > 1 && KoShapeFillWrapper(shapes, d->fillVariant).isMixedFill())) { Q_FOREACH (QAbstractButton *button, d->group->buttons()) { button->setEnabled(!shapes.isEmpty()); } } else { // only one vector object selected Q_FOREACH (QAbstractButton *button, d->group->buttons()) { button->setEnabled(true); } // update active index of shape KoShape *shape = shapes.first(); updateFillIndexFromShape(shape); updateFillColorFromShape(shape); // updates tool options fields shouldUploadColorToResourceManager = true; } // updates the UI d->group->button(d->selectedFillIndex)->setChecked(true); updateWidgetComponentVisbility(); slotUpdateFillTitle(); if (shouldUploadColorToResourceManager) { emit sigInternalRequestColorToResourceManager(); } else { emit sigInternalRecoverColorInResourceManager(); } } void KoFillConfigWidget::updateFillIndexFromShape(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(shape); KoShapeFillWrapper wrapper(shape, d->fillVariant); switch (wrapper.type()) { case KoFlake::None: d->selectedFillIndex = KoFillConfigWidget::None; break; case KoFlake::Solid: d->selectedFillIndex = KoFillConfigWidget::Solid; break; case KoFlake::Gradient: d->selectedFillIndex = KoFillConfigWidget::Gradient; break; case KoFlake::Pattern: d->selectedFillIndex = KoFillConfigWidget::Pattern; break; } } void KoFillConfigWidget::updateFillColorFromShape(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(shape); KoShapeFillWrapper wrapper(shape, d->fillVariant); switch (wrapper.type()) { case KoFlake::None: break; case KoFlake::Solid: { QColor color = wrapper.color(); if (color.alpha() > 0) { d->colorAction->setCurrentColor(wrapper.color()); } break; } case KoFlake::Gradient: uploadNewGradientBackground(wrapper.gradient()); updateGradientSaveButtonAvailability(); break; case KoFlake::Pattern: break; } } void KoFillConfigWidget::updateWidgetComponentVisbility() { // The UI is showing/hiding things like this because the 'stacked widget' isn't very flexible // and makes it difficult to put anything underneath it without a lot empty space // hide everything first d->ui->wdgGradientEditor->setVisible(false); d->ui->btnChoosePredefinedGradient->setVisible(false); d->ui->btnChooseSolidColor->setVisible(false); d->ui->typeLabel->setVisible(false); d->ui->repeatLabel->setVisible(false); d->ui->cmbGradientRepeat->setVisible(false); d->ui->cmbGradientType->setVisible(false); d->ui->btnSolidColorPick->setVisible(false); d->ui->btnSaveGradient->setVisible(false); d->ui->gradientTypeLine->setVisible(false); d->ui->soldStrokeColorLabel->setVisible(false); d->ui->presetLabel->setVisible(false); // keep options hidden if no vector shapes are selected if(currentShapes().isEmpty()) { return; } switch (d->selectedFillIndex) { case KoFillConfigWidget::None: break; case KoFillConfigWidget::Solid: d->ui->btnChooseSolidColor->setVisible(true); d->ui->btnSolidColorPick->setVisible(false); d->ui->soldStrokeColorLabel->setVisible(true); break; case KoFillConfigWidget::Gradient: d->ui->wdgGradientEditor->setVisible(true); d->ui->btnChoosePredefinedGradient->setVisible(true); d->ui->typeLabel->setVisible(true); d->ui->repeatLabel->setVisible(true); d->ui->cmbGradientRepeat->setVisible(true); d->ui->cmbGradientType->setVisible(true); d->ui->btnSaveGradient->setVisible(true); d->ui->gradientTypeLine->setVisible(true); d->ui->presetLabel->setVisible(true); break; case KoFillConfigWidget::Pattern: break; } } diff --git a/libs/ui/widgets/KoStrokeConfigWidget.cpp b/libs/ui/widgets/KoStrokeConfigWidget.cpp index a196bb02bf..cbdc24ca28 100644 --- a/libs/ui/widgets/KoStrokeConfigWidget.cpp +++ b/libs/ui/widgets/KoStrokeConfigWidget.cpp @@ -1,804 +1,800 @@ /* This file is part of the KDE project * Made by Tomislav Lukman (tomislav.lukman@ck.tel.hr) * Copyright (C) 2002 Tomislav Lukman * Copyright (C) 2002-2003 Rob Buis * Copyright (C) 2005-2006 Tim Beaulen * Copyright (C) 2005-2007 Thomas Zander * Copyright (C) 2005-2006, 2011 Inge Wallin * Copyright (C) 2005-2008 Jan Hambrecht * Copyright (C) 2006 C. Boemann * Copyright (C) 2006 Peter Simonsson * Copyright (C) 2006 Laurent Montel * Copyright (C) 2007,2011 Thorsten Zachmann * 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. */ // Own #include "KoStrokeConfigWidget.h" // Qt #include #include #include #include #include #include #include // KDE #include // Calligra #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_KoStrokeConfigWidget.h" #include #include #include "kis_canvas_resource_provider.h" #include "kis_acyclic_signal_connector.h" #include // Krita #include "kis_double_parse_unit_spin_box.h" class CapNJoinMenu : public QMenu { public: CapNJoinMenu(QWidget *parent = 0); QSize sizeHint() const override; KisDoubleParseUnitSpinBox *miterLimit {0}; QButtonGroup *capGroup {0}; QButtonGroup *joinGroup {0}; }; CapNJoinMenu::CapNJoinMenu(QWidget *parent) : QMenu(parent) { QGridLayout *mainLayout = new QGridLayout(); mainLayout->setMargin(2); // The cap group capGroup = new QButtonGroup(this); capGroup->setExclusive(true); QToolButton *button = 0; button = new QToolButton(this); button->setIcon(koIcon("stroke-cap-butt")); button->setCheckable(true); button->setToolTip(i18n("Butt cap")); capGroup->addButton(button, Qt::FlatCap); mainLayout->addWidget(button, 2, 0); button = new QToolButton(this); button->setIcon(koIcon("stroke-cap-round")); button->setCheckable(true); button->setToolTip(i18n("Round cap")); capGroup->addButton(button, Qt::RoundCap); mainLayout->addWidget(button, 2, 1); button = new QToolButton(this); button->setIcon(koIcon("stroke-cap-square")); button->setCheckable(true); button->setToolTip(i18n("Square cap")); capGroup->addButton(button, Qt::SquareCap); mainLayout->addWidget(button, 2, 2, Qt::AlignLeft); // The join group joinGroup = new QButtonGroup(this); joinGroup->setExclusive(true); button = new QToolButton(this); button->setIcon(koIcon("stroke-join-miter")); button->setCheckable(true); button->setToolTip(i18n("Miter join")); joinGroup->addButton(button, Qt::MiterJoin); mainLayout->addWidget(button, 3, 0); button = new QToolButton(this); button->setIcon(koIcon("stroke-join-round")); button->setCheckable(true); button->setToolTip(i18n("Round join")); joinGroup->addButton(button, Qt::RoundJoin); mainLayout->addWidget(button, 3, 1); button = new QToolButton(this); button->setIcon(koIcon("stroke-join-bevel")); button->setCheckable(true); button->setToolTip(i18n("Bevel join")); joinGroup->addButton(button, Qt::BevelJoin); mainLayout->addWidget(button, 3, 2, Qt::AlignLeft); // Miter limit // set min/max/step and value in points, then set actual unit miterLimit = new KisDoubleParseUnitSpinBox(this); miterLimit->setMinMaxStep(0.0, 1000.0, 0.5); miterLimit->setDecimals(2); miterLimit->setUnit(KoUnit(KoUnit::Point)); miterLimit->setToolTip(i18n("Miter limit")); mainLayout->addWidget(miterLimit, 4, 0, 1, 3); mainLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); setLayout(mainLayout); } QSize CapNJoinMenu::sizeHint() const { return layout()->sizeHint(); } class Q_DECL_HIDDEN KoStrokeConfigWidget::Private { public: Private() : selectionChangedCompressor(200, KisSignalCompressor::FIRST_ACTIVE) { } KoLineStyleSelector *lineStyle {0}; KisDoubleParseUnitSpinBox *lineWidth {0}; KoMarkerSelector *startMarkerSelector {0}; KoMarkerSelector *midMarkerSelector {0}; KoMarkerSelector *endMarkerSelector {0}; CapNJoinMenu *capNJoinMenu {0}; QWidget*spacer {0}; KoCanvasBase *canvas {0}; bool active {true}; bool allowLocalUnitManagement {false}; KoFillConfigWidget *fillConfigWidget {0}; bool noSelectionTrackingMode {false}; KisAcyclicSignalConnector shapeChangedAcyclicConnector; KisAcyclicSignalConnector resourceManagerAcyclicConnector; KisSignalCompressor selectionChangedCompressor; std::vector deactivationLocks; Ui_KoStrokeConfigWidget *ui; }; KoStrokeConfigWidget::KoStrokeConfigWidget(KoCanvasBase *canvas, QWidget * parent) : QWidget(parent) , d(new Private()) { // configure GUI d->ui = new Ui_KoStrokeConfigWidget(); d->ui->setupUi(this); setObjectName("Stroke widget"); { // connect the canvas d->shapeChangedAcyclicConnector.connectBackwardVoid( canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), &d->selectionChangedCompressor, SLOT(start())); d->shapeChangedAcyclicConnector.connectBackwardVoid( canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()), &d->selectionChangedCompressor, SLOT(start())); connect(&d->selectionChangedCompressor, SIGNAL(timeout()), this, SLOT(selectionChanged())); d->resourceManagerAcyclicConnector.connectBackwardResourcePair( canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), this, SLOT(canvasResourceChanged(int,QVariant))); d->canvas = canvas; } { d->fillConfigWidget = new KoFillConfigWidget(canvas, KoFlake::StrokeFill, true, this); d->fillConfigWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); d->ui->fillConfigWidgetLayout->addWidget(d->fillConfigWidget); connect(d->fillConfigWidget, SIGNAL(sigFillChanged()), SIGNAL(sigStrokeChanged())); } d->ui->thicknessLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); d->ui->thicknessLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); // set min/max/step and value in points, then set actual unit d->ui->lineWidth->setMinMaxStep(0.5, 1000.0, 0.5); // if someone wants 0, just set to "none" on UI d->ui->lineWidth->setDecimals(2); d->ui->lineWidth->setUnit(KoUnit(KoUnit::Point)); d->ui->lineWidth->setToolTip(i18n("Set line width of actual selection")); d->ui->capNJoinButton->setMinimumHeight(25); d->capNJoinMenu = new CapNJoinMenu(this); d->ui->capNJoinButton->setMenu(d->capNJoinMenu); d->ui->capNJoinButton->setText("..."); d->ui->capNJoinButton->setPopupMode(QToolButton::InstantPopup); { // Line style d->ui->strokeStyleLabel->setText(i18n("Line Style:")); d->ui->strokeStyleLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); d->ui->lineStyle->setToolTip(i18nc("@info:tooltip", "Line style")); d->ui->lineStyle->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); d->ui->lineStyle->setLineStyle(Qt::SolidLine, QVector()); } { QList emptyMarkers; d->startMarkerSelector = new KoMarkerSelector(KoFlake::StartMarker, this); d->startMarkerSelector->setToolTip(i18nc("@info:tooltip", "Start marker")); d->startMarkerSelector->updateMarkers(emptyMarkers); d->startMarkerSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred ); d->ui->markerLayout->addWidget(d->startMarkerSelector); d->midMarkerSelector = new KoMarkerSelector(KoFlake::MidMarker, this); d->midMarkerSelector->setToolTip(i18nc("@info:tooltip", "Node marker")); d->midMarkerSelector->updateMarkers(emptyMarkers); d->midMarkerSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred ); d->ui->markerLayout->addWidget(d->midMarkerSelector); d->endMarkerSelector = new KoMarkerSelector(KoFlake::EndMarker, this); d->endMarkerSelector->setToolTip(i18nc("@info:tooltip", "End marker")); d->endMarkerSelector->updateMarkers(emptyMarkers); d->endMarkerSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred ); d->ui->markerLayout->addWidget(d->endMarkerSelector); } // Spacer d->spacer = new QWidget(); d->spacer->setObjectName("SpecialSpacer"); d->ui->markerLayout->addWidget(d->spacer); connect(d->ui->lineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(applyDashStyleChanges())); connect(d->ui->lineWidth, SIGNAL(valueChangedPt(qreal)), this, SLOT(applyLineWidthChanges())); connect(d->capNJoinMenu->capGroup, SIGNAL(buttonClicked(int)), this, SLOT(applyJoinCapChanges())); connect(d->capNJoinMenu->joinGroup, SIGNAL(buttonClicked(int)), this, SLOT(applyJoinCapChanges())); connect(d->capNJoinMenu->miterLimit, SIGNAL(valueChangedPt(qreal)), this, SLOT(applyJoinCapChanges())); { // Map the marker signals correctly KisSignalMapper *mapper = new KisSignalMapper(this); connect(mapper, SIGNAL(mapped(int)), SLOT(applyMarkerChanges(int))); connect(d->startMarkerSelector, SIGNAL(currentIndexChanged(int)), mapper, SLOT(map())); connect(d->midMarkerSelector, SIGNAL(currentIndexChanged(int)), mapper, SLOT(map())); connect(d->endMarkerSelector, SIGNAL(currentIndexChanged(int)), mapper, SLOT(map())); mapper->setMapping(d->startMarkerSelector, KoFlake::StartMarker); mapper->setMapping(d->midMarkerSelector, KoFlake::MidMarker); mapper->setMapping(d->endMarkerSelector, KoFlake::EndMarker); } KoDocumentResourceManager *resourceManager = canvas->shapeController()->resourceManager(); if (resourceManager) { KoMarkerCollection *collection = resourceManager->resource(KoDocumentResourceManager::MarkerCollection).value(); if (collection) { updateMarkers(collection->markers()); } } d->selectionChangedCompressor.start(); d->fillConfigWidget->activate(); deactivate(); } KoStrokeConfigWidget::~KoStrokeConfigWidget() { delete d; } void KoStrokeConfigWidget::setNoSelectionTrackingMode(bool value) { d->fillConfigWidget->setNoSelectionTrackingMode(value); d->noSelectionTrackingMode = value; if (!d->noSelectionTrackingMode) { d->selectionChangedCompressor.start(); } } // ---------------------------------------------------------------- // getters and setters Qt::PenStyle KoStrokeConfigWidget::lineStyle() const { return d->ui->lineStyle->lineStyle(); } QVector KoStrokeConfigWidget::lineDashes() const { return d->ui->lineStyle->lineDashes(); } qreal KoStrokeConfigWidget::lineWidth() const { return d->ui->lineWidth->value(); } qreal KoStrokeConfigWidget::miterLimit() const { return d->capNJoinMenu->miterLimit->value(); } KoMarker *KoStrokeConfigWidget::startMarker() const { return d->startMarkerSelector->marker(); } KoMarker *KoStrokeConfigWidget::endMarker() const { return d->endMarkerSelector->marker(); } Qt::PenCapStyle KoStrokeConfigWidget::capStyle() const { return static_cast(d->capNJoinMenu->capGroup->checkedId()); } Qt::PenJoinStyle KoStrokeConfigWidget::joinStyle() const { return static_cast(d->capNJoinMenu->joinGroup->checkedId()); } KoShapeStrokeSP KoStrokeConfigWidget::createShapeStroke() { KoShapeStrokeSP stroke(d->fillConfigWidget->createShapeStroke()); stroke->setLineWidth(lineWidth()); stroke->setCapStyle(capStyle()); stroke->setJoinStyle(joinStyle()); stroke->setMiterLimit(miterLimit()); stroke->setLineStyle(lineStyle(), lineDashes()); return stroke; } // ---------------------------------------------------------------- // Other public functions void KoStrokeConfigWidget::updateStyleControlsAvailability(bool enabled) { d->ui->lineWidth->setEnabled(enabled); d->capNJoinMenu->setEnabled(enabled); d->ui->lineStyle->setEnabled(enabled); d->startMarkerSelector->setEnabled(enabled); d->midMarkerSelector->setEnabled(enabled); d->endMarkerSelector->setEnabled(enabled); } void KoStrokeConfigWidget::setUnit(const KoUnit &unit, KoShape *representativeShape) { if (!d->allowLocalUnitManagement) { return; //the unit management is completely transferred to the unitManagers. } blockChildSignals(true); /** * KoStrokeShape knows nothing about the transformations applied * to the shape, which doesn't prevent the shape to apply them and * display the stroke differently. So just take that into account * and show the user correct values using the multiplier in KoUnit. */ KoUnit newUnit(unit); if (representativeShape) { newUnit.adjustByPixelTransform(representativeShape->absoluteTransformation()); } d->ui->lineWidth->setUnit(newUnit); d->capNJoinMenu->miterLimit->setUnit(newUnit); d->ui->lineWidth->setLineStep(1.0); d->capNJoinMenu->miterLimit->setLineStep(1.0); blockChildSignals(false); } void KoStrokeConfigWidget::setUnitManagers(KisSpinBoxUnitManager* managerLineWidth, KisSpinBoxUnitManager *managerMitterLimit) { blockChildSignals(true); d->allowLocalUnitManagement = false; d->ui->lineWidth->setUnitManager(managerLineWidth); d->capNJoinMenu->miterLimit->setUnitManager(managerMitterLimit); blockChildSignals(false); } void KoStrokeConfigWidget::updateMarkers(const QList &markers) { d->startMarkerSelector->updateMarkers(markers); d->midMarkerSelector->updateMarkers(markers); d->endMarkerSelector->updateMarkers(markers); } void KoStrokeConfigWidget::activate() { - KIS_SAFE_ASSERT_RECOVER_RETURN(!d->deactivationLocks.empty()); d->deactivationLocks.clear(); d->fillConfigWidget->activate(); - if (!d->noSelectionTrackingMode) { - // selectionChanged(); d->selectionChangedCompressor.start(); } else { loadCurrentStrokeFillFromResourceServer(); } } void KoStrokeConfigWidget::deactivate() { - KIS_SAFE_ASSERT_RECOVER_RETURN(d->deactivationLocks.empty()); - + d->deactivationLocks.clear(); d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->shapeChangedAcyclicConnector)); d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->resourceManagerAcyclicConnector)); d->fillConfigWidget->deactivate(); } void KoStrokeConfigWidget::blockChildSignals(bool block) { d->ui->lineWidth->blockSignals(block); d->capNJoinMenu->capGroup->blockSignals(block); d->capNJoinMenu->joinGroup->blockSignals(block); d->capNJoinMenu->miterLimit->blockSignals(block); d->ui->lineStyle->blockSignals(block); d->startMarkerSelector->blockSignals(block); d->midMarkerSelector->blockSignals(block); d->endMarkerSelector->blockSignals(block); } void KoStrokeConfigWidget::setActive(bool active) { d->active = active; } //------------------------ template auto applyChangeToStrokes(KoCanvasBase *canvas, ModifyFunction modifyFunction) -> decltype(modifyFunction(KoShapeStrokeSP()), void()) { KoSelection *selection = canvas->selectedShapesProxy()->selection(); if (!selection) return; QList shapes = selection->selectedEditableShapes(); KUndo2Command *command = KoFlake::modifyShapesStrokes(shapes, modifyFunction); if (command) { canvas->addCommand(command); } } void KoStrokeConfigWidget::applyDashStyleChanges() { applyChangeToStrokes( d->canvas, [this] (KoShapeStrokeSP stroke) { stroke->setLineStyle(lineStyle(), lineDashes()); }); emit sigStrokeChanged(); } void KoStrokeConfigWidget::applyLineWidthChanges() { applyChangeToStrokes( d->canvas, [this] (KoShapeStrokeSP stroke) { stroke->setLineWidth(lineWidth()); }); emit sigStrokeChanged(); } void KoStrokeConfigWidget::applyJoinCapChanges() { applyChangeToStrokes( d->canvas, [this] (KoShapeStrokeSP stroke) { stroke->setCapStyle(static_cast(d->capNJoinMenu->capGroup->checkedId())); stroke->setJoinStyle(static_cast(d->capNJoinMenu->joinGroup->checkedId())); stroke->setMiterLimit(miterLimit()); }); emit sigStrokeChanged(); } void KoStrokeConfigWidget::applyMarkerChanges(int rawPosition) { KoSelection *selection = d->canvas->selectedShapesProxy()->selection(); if (!selection) { emit sigStrokeChanged(); return; } QList shapes = selection->selectedEditableShapes(); QList pathShapes; Q_FOREACH (KoShape *shape, shapes) { KoPathShape *pathShape = dynamic_cast(shape); if (pathShape) { pathShapes << pathShape; } } if (pathShapes.isEmpty()) { emit sigStrokeChanged(); return; } KoFlake::MarkerPosition position = KoFlake::MarkerPosition(rawPosition); QScopedPointer marker; switch (position) { case KoFlake::StartMarker: if (d->startMarkerSelector->marker()) { marker.reset(new KoMarker(*d->startMarkerSelector->marker())); } break; case KoFlake::MidMarker: if (d->midMarkerSelector->marker()) { marker.reset(new KoMarker(*d->midMarkerSelector->marker())); } break; case KoFlake::EndMarker: if (d->endMarkerSelector->marker()) { marker.reset(new KoMarker(*d->endMarkerSelector->marker())); } break; } KUndo2Command* command = new KoPathShapeMarkerCommand(pathShapes, marker.take(), position); d->canvas->addCommand(command); emit sigStrokeChanged(); } // ---------------------------------------------------------------- struct CheckShapeStrokeStyleBasePolicy { typedef KoShapeStrokeSP PointerType; static PointerType getProperty(KoShape *shape) { return qSharedPointerDynamicCast(shape->stroke()); } }; struct CheckShapeStrokeDashesPolicy : public CheckShapeStrokeStyleBasePolicy { static bool compareTo(PointerType p1, PointerType p2) { return p1->lineStyle() == p2->lineStyle() && p1->lineDashes() == p2->lineDashes() && p1->dashOffset() == p2->dashOffset(); } }; struct CheckShapeStrokeCapJoinPolicy : public CheckShapeStrokeStyleBasePolicy { static bool compareTo(PointerType p1, PointerType p2) { return p1->capStyle() == p2->capStyle() && p1->joinStyle() == p2->joinStyle() && p1->miterLimit() == p2->miterLimit(); } }; struct CheckShapeStrokeWidthPolicy : public CheckShapeStrokeStyleBasePolicy { static bool compareTo(PointerType p1, PointerType p2) { return p1->lineWidth() == p2->lineWidth(); } }; struct CheckShapeMarkerPolicy { CheckShapeMarkerPolicy(KoFlake::MarkerPosition position) : m_position(position) { } typedef KoMarker* PointerType; PointerType getProperty(KoShape *shape) const { KoPathShape *pathShape = dynamic_cast(shape); return pathShape ? pathShape->marker(m_position) : 0; } bool compareTo(PointerType p1, PointerType p2) const { if ((!p1 || !p2) && p1 != p2) return false; if (!p1 && p1 == p2) return true; return p1 == p2 || *p1 == *p2; } KoFlake::MarkerPosition m_position; }; void KoStrokeConfigWidget::selectionChanged() { if (d->noSelectionTrackingMode) return; KoSelection *selection = d->canvas->selectedShapesProxy()->selection(); if (!selection) return; // we need to linearize update order, and force the child widget to update // before we start doing it QList shapes = selection->selectedEditableShapes(); d->fillConfigWidget->forceUpdateOnSelectionChanged(); // calls shapeChanged() logic KoShape *shape = !shapes.isEmpty() ? shapes.first() : 0; const KoShapeStrokeSP stroke = shape ? qSharedPointerDynamicCast(shape->stroke()) : KoShapeStrokeSP(); // setUnit uses blockChildSignals() so take care not to use it inside the block setUnit(d->canvas->unit(), shape); blockChildSignals(true); // line width if (stroke && KoFlake::compareShapePropertiesEqual(shapes)) { d->ui->lineWidth->changeValue(stroke->lineWidth()); } else { d->ui->lineWidth->changeValue(0); } // caps & joins if (stroke && KoFlake::compareShapePropertiesEqual(shapes)) { Qt::PenCapStyle capStyle = stroke->capStyle() >= 0 ? stroke->capStyle() : Qt::FlatCap; Qt::PenJoinStyle joinStyle = stroke->joinStyle() >= 0 ? stroke->joinStyle() : Qt::MiterJoin; { QAbstractButton *button = d->capNJoinMenu->capGroup->button(capStyle); KIS_SAFE_ASSERT_RECOVER_RETURN(button); button->setChecked(true); } { QAbstractButton *button = d->capNJoinMenu->joinGroup->button(joinStyle); KIS_SAFE_ASSERT_RECOVER_RETURN(button); button->setChecked(true); } d->capNJoinMenu->miterLimit->changeValue(stroke->miterLimit()); d->capNJoinMenu->miterLimit->setEnabled(joinStyle == Qt::MiterJoin); } else { d->capNJoinMenu->capGroup->button(Qt::FlatCap)->setChecked(true); d->capNJoinMenu->joinGroup->button(Qt::MiterJoin)->setChecked(true); d->capNJoinMenu->miterLimit->changeValue(0.0); d->capNJoinMenu->miterLimit->setEnabled(true); } // dashes style if (stroke && KoFlake::compareShapePropertiesEqual(shapes)) { d->ui->lineStyle->setLineStyle(stroke->lineStyle(), stroke->lineDashes()); } else { d->ui->lineStyle->setLineStyle(Qt::SolidLine, QVector()); } // markers KoPathShape *pathShape = dynamic_cast(shape); if (pathShape) { if (KoFlake::compareShapePropertiesEqual(shapes, CheckShapeMarkerPolicy(KoFlake::StartMarker))) { d->startMarkerSelector->setMarker(pathShape->marker(KoFlake::StartMarker)); } if (KoFlake::compareShapePropertiesEqual(shapes, CheckShapeMarkerPolicy(KoFlake::MidMarker))) { d->midMarkerSelector->setMarker(pathShape->marker(KoFlake::MidMarker)); } if (KoFlake::compareShapePropertiesEqual(shapes, CheckShapeMarkerPolicy(KoFlake::EndMarker))) { d->endMarkerSelector->setMarker(pathShape->marker(KoFlake::EndMarker)); } } const bool lineOptionsVisible = (d->fillConfigWidget->selectedFillIndex() != 0); // This switch statement is to help the tab widget "pages" to be closer to the correct size // if we don't do this the internal widgets get rendered, then the tab page has to get resized to // fill up the space, then the internal widgets have to resize yet again...causing flicker switch(d->fillConfigWidget->selectedFillIndex()) { case 0: // no fill this->setMinimumHeight(130); break; case 1: // solid fill this->setMinimumHeight(200); break; case 2: // gradient fill this->setMinimumHeight(350); case 3: // pattern fill break; } d->ui->thicknessLineBreak->setVisible(lineOptionsVisible); d->ui->lineWidth->setVisible(lineOptionsVisible); d->ui->capNJoinButton->setVisible(lineOptionsVisible); d->ui->lineStyle->setVisible(lineOptionsVisible); d->startMarkerSelector->setVisible(lineOptionsVisible); d->midMarkerSelector->setVisible(lineOptionsVisible); d->endMarkerSelector->setVisible(lineOptionsVisible); d->ui->thicknessLabel->setVisible(lineOptionsVisible); d->ui->strokeStyleLabel->setVisible(lineOptionsVisible); blockChildSignals(false); updateStyleControlsAvailability(!shapes.isEmpty()); } void KoStrokeConfigWidget::canvasResourceChanged(int key, const QVariant &value) { switch (key) { case KoCanvasResourceProvider::Unit: // we request the whole selection to reload because the // unit of the stroke width depends on the selected shape d->selectionChangedCompressor.start(); break; case KisCanvasResourceProvider::Size: if (d->noSelectionTrackingMode) { d->ui->lineWidth->changeValue(d->canvas->unit().fromUserValue(value.toReal())); } break; } } void KoStrokeConfigWidget::loadCurrentStrokeFillFromResourceServer() { if (d->canvas) { const QVariant value = d->canvas->resourceManager()->resource(KisCanvasResourceProvider::Size); canvasResourceChanged(KisCanvasResourceProvider::Size, value); updateStyleControlsAvailability(true); emit sigStrokeChanged(); } } diff --git a/libs/ui/widgets/kis_advanced_color_space_selector.cc b/libs/ui/widgets/kis_advanced_color_space_selector.cc index 2550e89840..3fbc2b1ada 100644 --- a/libs/ui/widgets/kis_advanced_color_space_selector.cc +++ b/libs/ui/widgets/kis_advanced_color_space_selector.cc @@ -1,795 +1,795 @@ /* * Copyright (C) 2007 Cyrille Berger * Copyright (C) 2011 Boudewijn Rempt * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (C) 2015 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_advanced_color_space_selector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgcolorspaceselectoradvanced.h" #include struct KisAdvancedColorSpaceSelector::Private { Ui_WdgColorSpaceSelectorAdvanced* colorSpaceSelector; QString knsrcFile; }; KisAdvancedColorSpaceSelector::KisAdvancedColorSpaceSelector(QWidget* parent, const QString &caption) : QDialog(parent) , d(new Private) { setWindowTitle(caption); d->colorSpaceSelector = new Ui_WdgColorSpaceSelectorAdvanced; d->colorSpaceSelector->setupUi(this); d->colorSpaceSelector->cmbColorModels->setIDList(KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::OnlyUserVisible)); fillCmbDepths(d->colorSpaceSelector->cmbColorModels->currentItem()); d->colorSpaceSelector->bnInstallProfile->setIcon(KisIconUtils::loadIcon("document-open")); d->colorSpaceSelector->bnInstallProfile->setToolTip( i18n("Open Color Profile") ); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(KoID)), this, SLOT(fillCmbDepths(KoID))); connect(d->colorSpaceSelector->cmbColorDepth, SIGNAL(activated(KoID)), this, SLOT(fillLstProfiles())); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(KoID)), this, SLOT(fillLstProfiles())); connect(d->colorSpaceSelector->lstProfile, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(colorSpaceChanged())); connect(this, SIGNAL(selectionChanged(bool)), this, SLOT(fillDescription())); connect(this, SIGNAL(selectionChanged(bool)), d->colorSpaceSelector->TongueWidget, SLOT(repaint())); connect(this, SIGNAL(selectionChanged(bool)), d->colorSpaceSelector->TRCwidget, SLOT(repaint())); connect(d->colorSpaceSelector->bnInstallProfile, SIGNAL(clicked()), this, SLOT(installProfile())); connect(d->colorSpaceSelector->bnOK, SIGNAL(accepted()), this, SLOT(accept())); connect(d->colorSpaceSelector->bnOK, SIGNAL(rejected()), this, SLOT(reject())); fillLstProfiles(); } KisAdvancedColorSpaceSelector::~KisAdvancedColorSpaceSelector() { delete d->colorSpaceSelector; delete d; } void KisAdvancedColorSpaceSelector::fillLstProfiles() { d->colorSpaceSelector->lstProfile->blockSignals(true); const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); const QString defaultProfileName = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId); d->colorSpaceSelector->lstProfile->clear(); QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); QStringList profileNames; Q_FOREACH (const KoColorProfile *profile, profileList) { profileNames.append(profile->name()); } std::sort(profileNames.begin(), profileNames.end()); QListWidgetItem *defaultProfile = new QListWidgetItem; defaultProfile->setText(defaultProfileName + " " + i18nc("This is appended to the color profile which is the default for the given colorspace and bit-depth","(Default)")); Q_FOREACH (QString stringName, profileNames) { if (stringName == defaultProfileName) { d->colorSpaceSelector->lstProfile->addItem(defaultProfile); } else { d->colorSpaceSelector->lstProfile->addItem(stringName); } } d->colorSpaceSelector->lstProfile->setCurrentItem(defaultProfile); d->colorSpaceSelector->lstProfile->blockSignals(false); colorSpaceChanged(); } void KisAdvancedColorSpaceSelector::fillCmbDepths(const KoID& id) { KoID activeDepth = d->colorSpaceSelector->cmbColorDepth->currentItem(); d->colorSpaceSelector->cmbColorDepth->clear(); QList depths = KoColorSpaceRegistry::instance()->colorDepthList(id, KoColorSpaceRegistry::OnlyUserVisible); QList sortedDepths; if (depths.contains(Integer8BitsColorDepthID)) { sortedDepths << Integer8BitsColorDepthID; } if (depths.contains(Integer16BitsColorDepthID)) { sortedDepths << Integer16BitsColorDepthID; } if (depths.contains(Float16BitsColorDepthID)) { sortedDepths << Float16BitsColorDepthID; } if (depths.contains(Float32BitsColorDepthID)) { sortedDepths << Float32BitsColorDepthID; } if (depths.contains(Float64BitsColorDepthID)) { sortedDepths << Float64BitsColorDepthID; } d->colorSpaceSelector->cmbColorDepth->setIDList(sortedDepths); if (sortedDepths.contains(activeDepth)) { d->colorSpaceSelector->cmbColorDepth->setCurrent(activeDepth); } } void KisAdvancedColorSpaceSelector::fillDescription() { QString notApplicable = i18nc("Not Applicable, used where there's no colorants or gamma curve found","N/A"); QString notApplicableTooltip = i18nc("@info:tooltip","This profile has no colorants."); QString profileName = i18nc("Shows up instead of the name when there's no profile","No Profile Found"); QString whatIsColorant = i18n("Colorant in d50-adapted xyY."); //set colorants const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); if (!profileList.isEmpty()) { profileName = currentColorSpace()->profile()->name(); if (currentColorSpace()->profile()->hasColorants()){ QVector colorants = currentColorSpace()->profile()->getColorantsxyY(); QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); //QString text = currentColorSpace()->profile()->info() + " =" + d->colorSpaceSelector->lblXYZ_W->setText(nameWhitePoint(whitepoint)); d->colorSpaceSelector->lblXYZ_W->setToolTip(QString::number(whitepoint[0], 'f', 4) + ", " + QString::number(whitepoint[1], 'f', 4) + ", " + QString::number(whitepoint[2], 'f', 4)); d->colorSpaceSelector->TongueWidget->setToolTip("
      "+i18nc("@info:tooltip","This profile has the following xyY colorants:")+"
      "+ i18n("Red:") +""+QString::number(colorants[0], 'f', 4) + "" + QString::number(colorants[1], 'f', 4) + "" + QString::number(colorants[2], 'f', 4)+"
      "+ i18n("Green:")+""+QString::number(colorants[3], 'f', 4) + "" + QString::number(colorants[4], 'f', 4) + "" + QString::number(colorants[5], 'f', 4)+"
      "+ i18n("Blue:") +""+QString::number(colorants[6], 'f', 4) + "" + QString::number(colorants[7], 'f', 4) + "" + QString::number(colorants[8], 'f', 4)+"
      "); } else { QVector whitepoint2 = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->lblXYZ_W->setText(nameWhitePoint(whitepoint2)); d->colorSpaceSelector->lblXYZ_W->setToolTip(QString::number(whitepoint2[0], 'f', 4) + ", " + QString::number(whitepoint2[1], 'f', 4) + ", " + QString::number(whitepoint2[2], 'f', 4)); d->colorSpaceSelector->TongueWidget->setToolTip(notApplicableTooltip); } } else { d->colorSpaceSelector->lblXYZ_W->setText(notApplicable); d->colorSpaceSelector->lblXYZ_W->setToolTip(notApplicableTooltip); d->colorSpaceSelector->TongueWidget->setToolTip(notApplicableTooltip); } //set TRC QVector estimatedTRC(3); QString estimatedGamma = i18nc("Estimated Gamma indicates how the TRC (Tone Response Curve or Tone Reproduction Curve) is bent. A Gamma of 1.0 means linear.", "Estimated Gamma: "); QString estimatedsRGB = i18nc("This is for special Gamma types that LCMS cannot differentiate between", "Estimated Gamma: sRGB, L* or rec709 TRC"); QString whatissRGB = i18nc("@info:tooltip","The Tone Response Curve of this color space is either sRGB, L* or rec709 TRC."); QString currentModelStr = d->colorSpaceSelector->cmbColorModels->currentItem().id(); if (profileList.isEmpty()) { d->colorSpaceSelector->TongueWidget->setProfileDataAvailable(false); d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } else if (currentModelStr == "RGBA") { QVector colorants = currentColorSpace()->profile()->getColorantsxyY(); QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); if (currentColorSpace()->profile()->hasColorants()){ d->colorSpaceSelector->TongueWidget->setRGBData(whitepoint, colorants); } else { colorants.fill(0.0); d->colorSpaceSelector->TongueWidget->setRGBData(whitepoint, colorants); } d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF redcurve; QPolygonF greencurve; QPolygonF bluecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); redcurve<colorSpaceSelector->TRCwidget->setRGBCurve(redcurve, greencurve, bluecurve); } else { QPolygonF curve = currentColorSpace()->estimatedTRCXYY(); redcurve << curve.at(0) << curve.at(1) << curve.at(2) << curve.at(3) << curve.at(4); greencurve << curve.at(5) << curve.at(6) << curve.at(7) << curve.at(8) << curve.at(9); bluecurve << curve.at(10) << curve.at(11) << curve.at(12) << curve.at(13) << curve.at(14); d->colorSpaceSelector->TRCwidget->setRGBCurve(redcurve, greencurve, bluecurve); } if (estimatedTRC[0] == -1) { d->colorSpaceSelector->TRCwidget->setToolTip(""+whatissRGB+"
      "+estimatedCurve+""); } else { d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0]) + "," + QString::number(estimatedTRC[1]) + "," + QString::number(estimatedTRC[2])+"
      "+estimatedCurve+""); } } else if (currentModelStr == "GRAYA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setGrayData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } d->colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); if (estimatedTRC[0] == -1) { d->colorSpaceSelector->TRCwidget->setToolTip(""+whatissRGB+"
      "+estimatedCurve+""); } else { d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0])+"
      "+estimatedCurve+""); } } else if (currentModelStr == "CMYKA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setCMYKData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; QPolygonF cyancurve; QPolygonF magentacurve; QPolygonF yellowcurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { QPolygonF curve = currentColorSpace()->estimatedTRCXYY(); cyancurve << curve.at(0) << curve.at(1) << curve.at(2) << curve.at(3) << curve.at(4); magentacurve << curve.at(5) << curve.at(6) << curve.at(7) << curve.at(8) << curve.at(9); yellowcurve << curve.at(10) << curve.at(11) << curve.at(12) << curve.at(13) << curve.at(14); tonecurve << curve.at(15) << curve.at(16) << curve.at(17) << curve.at(18) << curve.at(19); d->colorSpaceSelector->TRCwidget->setCMYKCurve(cyancurve, magentacurve, yellowcurve, tonecurve); } d->colorSpaceSelector->TRCwidget->setToolTip(i18nc("@info:tooltip","Estimated Gamma cannot be retrieved for CMYK.")); } else if (currentModelStr == "XYZA") { QString estimatedCurve = " Estimated curve: "; estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setXYZData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0])+"< br />"+estimatedCurve+""); } else if (currentModelStr == "LABA") { estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setLABData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(""+i18nc("@info:tooltip","This is assumed to be the L * TRC. ")+"
      "+estimatedCurve+""); } else if (currentModelStr == "YCbCrA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setYCbCrData(whitepoint); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(i18nc("@info:tooltip","Estimated Gamma cannot be retrieved for YCrCb.")); } d->colorSpaceSelector->textProfileDescription->clear(); if (profileList.isEmpty()==false) { d->colorSpaceSelector->textProfileDescription->append("

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

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

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

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

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

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

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

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

      " + profileName + "

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Extra notes on profiles by Elle Stone:

    " "

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

    ")); + "Elle Stone. This is a summary. Please check " + "the full documentation as well.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    ")); } } d->colorSpaceSelector->textProfileDescription->moveCursor(QTextCursor::Start); } QString KisAdvancedColorSpaceSelector::nameWhitePoint(QVector whitePoint) { QString name=(QString::number(whitePoint[0]) + ", " + QString::number(whitePoint[1], 'f', 4)); //A (0.451170, 0.40594) (2856K)(tungsten) if ((whitePoint[0]>0.451170-0.005 && whitePoint[0]<0.451170 + 0.005) && (whitePoint[1]>0.40594-0.005 && whitePoint[1]<0.40594 + 0.005)){ name="A"; return name; } //B (0.34980, 0.35270) (4874K) (Direct Sunlight at noon)(obsolete) //C (0.31039, 0.31905) (6774K) (average/north sky daylight)(obsolete) //D50 (0.34773, 0.35952) (5003K) (Horizon Light, default color of white paper, ICC profile standard illuminant) if ((whitePoint[0]>0.34773-0.005 && whitePoint[0]<0.34773 + 0.005) && (whitePoint[1]>0.35952-0.005 && whitePoint[1]<0.35952 + 0.005)){ name="D50"; return name; } //D55 (0.33411, 0.34877) (5503K) (Mid-morning / Mid-afternoon Daylight) if ((whitePoint[0]>0.33411-0.001 && whitePoint[0]<0.33411 + 0.001) && (whitePoint[1]>0.34877-0.005 && whitePoint[1]<0.34877 + 0.005)){ name="D55"; return name; } //D60 (0.3217, 0.3378) (~6000K) (ACES colorspace default) if ((whitePoint[0]>0.3217-0.001 && whitePoint[0]<0.3217 + 0.001) && (whitePoint[1]>0.3378-0.005 && whitePoint[1]<0.3378 + 0.005)){ name="D60"; return name; } //D65 (0.31382, 0.33100) (6504K) (Noon Daylight, default for computer and tv screens, sRGB default) //Elle's are old school with 0.3127 and 0.3289 if ((whitePoint[0]>0.31382-0.002 && whitePoint[0]<0.31382 + 0.002) && (whitePoint[1]>0.33100-0.005 && whitePoint[1]<0.33100 + 0.002)){ name="D65"; return name; } //D75 (0.29968, 0.31740) (7504K) (North sky Daylight) if ((whitePoint[0]>0.29968-0.001 && whitePoint[0]<0.29968 + 0.001) && (whitePoint[1]>0.31740-0.005 && whitePoint[1]<0.31740 + 0.005)){ name="D75"; return name; } //E (1/3, 1/3) (5454K) (Equal Energy. CIERGB default) if ((whitePoint[0]>(1.0/3.0)-0.001 && whitePoint[0]<(1.0/3.0) + 0.001) && (whitePoint[1]>(1.0/3.0)-0.001 && whitePoint[1]<(1.0/3.0) + 0.001)){ name="E"; return name; } //The F series seems to sorta overlap with the D series, so I'll just leave them in comment here.// //F1 (0.31811, 0.33559) (6430K) (Daylight Fluorescent) //F2 (0.37925, 0.36733) (4230K) (Cool White Fluorescent) //F3 (0.41761, 0.38324) (3450K) (White Fluorescent) //F4 (0.44920, 0.39074) (2940K) (Warm White Fluorescent) //F5 (0.31975, 0.34246) (6350K) (Daylight Fluorescent) //F6 (0.38660, 0.37847) (4150K) (Lite White Fluorescent) //F7 (0.31569, 0.32960) (6500K) (D65 simulator, Daylight simulator) //F8 (0.34902, 0.35939) (5000K) (D50 simulator) //F9 (0.37829, 0.37045) (4150K) (Cool White Deluxe Fluorescent) //F10 (0.35090, 0.35444) (5000K) (Philips TL85, Ultralume 50) //F11 (0.38541, 0.37123) (4000K) (Philips TL84, Ultralume 40) //F12 (0.44256, 0.39717) (3000K) (Philips TL83, Ultralume 30) return name; } const KoColorSpace* KisAdvancedColorSpaceSelector::currentColorSpace() { QString check = ""; if (d->colorSpaceSelector->lstProfile->currentItem()) { check = d->colorSpaceSelector->lstProfile->currentItem()->text(); } else if (d->colorSpaceSelector->lstProfile->item(0)) { check = d->colorSpaceSelector->lstProfile->item(0)->text(); } return KoColorSpaceRegistry::instance()->colorSpace(d->colorSpaceSelector->cmbColorModels->currentItem().id(), d->colorSpaceSelector->cmbColorDepth->currentItem().id(), check); } void KisAdvancedColorSpaceSelector::setCurrentColorModel(const KoID& id) { d->colorSpaceSelector->cmbColorModels->setCurrent(id); fillLstProfiles(); fillCmbDepths(id); } void KisAdvancedColorSpaceSelector::setCurrentColorDepth(const KoID& id) { d->colorSpaceSelector->cmbColorDepth->setCurrent(id); fillLstProfiles(); } void KisAdvancedColorSpaceSelector::setCurrentProfile(const QString& name) { QList Items= d->colorSpaceSelector->lstProfile->findItems(name, Qt::MatchStartsWith); d->colorSpaceSelector->lstProfile->setCurrentItem(Items.at(0)); } void KisAdvancedColorSpaceSelector::setCurrentColorSpace(const KoColorSpace* colorSpace) { if (!colorSpace) { return; } setCurrentColorModel(colorSpace->colorModelId()); setCurrentColorDepth(colorSpace->colorDepthId()); setCurrentProfile(colorSpace->profile()->name()); } void KisAdvancedColorSpaceSelector::colorSpaceChanged() { bool valid = d->colorSpaceSelector->lstProfile->count() != 0; emit(selectionChanged(valid)); if (valid) { emit colorSpaceChanged(currentColorSpace()); } } void KisAdvancedColorSpaceSelector::installProfile() { KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { QUrl file(profileName); if (!QFile::copy(profileName, saveLocation + file.fileName())) { dbgKrita << "Could not install profile!"; return; } iccEngine->addProfile(saveLocation + file.fileName()); } fillLstProfiles(); } diff --git a/libs/ui/widgets/kis_cmb_gradient.cpp b/libs/ui/widgets/kis_cmb_gradient.cpp index f46b3f39a1..e98cb40be0 100644 --- a/libs/ui/widgets/kis_cmb_gradient.cpp +++ b/libs/ui/widgets/kis_cmb_gradient.cpp @@ -1,78 +1,77 @@ /* * 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 "kis_cmb_gradient.h" #include #include #include #include #include #include #include #include "kis_gradient_chooser.h" KisCmbGradient::KisCmbGradient(QWidget *parent) : KisPopupButton(parent) , m_gradientChooser(new KisGradientChooser(this)) { connect(m_gradientChooser, SIGNAL(resourceSelected(KoResource*)), SLOT(gradientSelected(KoResource*))); setPopupWidget(m_gradientChooser); } void KisCmbGradient::setGradient(KoAbstractGradient *gradient) { m_gradientChooser->setCurrentResource(gradient); } KoAbstractGradient *KisCmbGradient::gradient() const { return dynamic_cast(m_gradientChooser->currentResource()); } void KisCmbGradient::gradientSelected(KoResource *resource) { KoAbstractGradient *gradient = dynamic_cast(resource); if (!gradient) return; QImage pm = gradient->generatePreview(iconSize().width(), iconSize().height()); setIcon(QIcon(QPixmap::fromImage(pm))); emit gradientChanged(gradient); } QSize KisCmbGradient::sizeHint() const { ensurePolished(); QFontMetrics fm = fontMetrics(); int maxW = 7 * fm.width(QChar('x')) + 18; int maxH = qMax(fm.lineSpacing(), 14) + 2; QStyleOptionComboBox options; options.initFrom(this); - return style()->sizeFromContents(QStyle::CT_ComboBox, &options, - QSize(maxW, maxH), this).expandedTo(QApplication::globalStrut()); + return style()->sizeFromContents(QStyle::CT_ComboBox, &options, QSize(maxW, maxH), this); } void KisCmbGradient::resizeEvent(QResizeEvent *event) { setIconSize(QSize(event->size().width() - 30, event->size().height() - 4)); KisPopupButton::resizeEvent(event); } diff --git a/libs/ui/widgets/kis_color_filter_combo.cpp b/libs/ui/widgets/kis_color_filter_combo.cpp index 518d2e8eb7..224054391f 100644 --- a/libs/ui/widgets/kis_color_filter_combo.cpp +++ b/libs/ui/widgets/kis_color_filter_combo.cpp @@ -1,348 +1,371 @@ /* * 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_color_filter_combo.h" #include #include #include #include +#include #include #include #include #include #include #include "kis_node_view_color_scheme.h" #include "kis_debug.h" #include "kis_icon_utils.h" #include "krita_utils.h" #include "kis_node.h" enum AdditionalRoles { OriginalLabelIndex = Qt::UserRole + 1000 }; struct LabelFilteringModel : public QSortFilterProxyModel { LabelFilteringModel(QObject *parent) : QSortFilterProxyModel(parent) {} bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override { const QModelIndex index = sourceModel()->index(source_row, 0, source_parent); const int labelIndex = index.data(OriginalLabelIndex).toInt(); return labelIndex < 0 || m_acceptedLabels.contains(labelIndex); } void setAcceptedLabels(const QSet &value) { m_acceptedLabels = value; invalidateFilter(); } private: QSet m_acceptedLabels; }; class ComboEventFilter : public QObject { public: ComboEventFilter(KisColorFilterCombo *parent) : m_parent(parent), m_buttonPressed(false) {} protected: bool eventFilter(QObject *obj, QEvent *event) override { if (event->type() == QEvent::Leave) { m_buttonPressed = false; } else if (event->type() == QEvent::MouseButtonPress) { QMouseEvent *mevent = static_cast(event); m_buttonPressed = mevent->button() == Qt::LeftButton; } else if (event->type() == QEvent::MouseButtonRelease) { QMouseEvent *mevent = static_cast(event); QModelIndex index = m_parent->view()->indexAt(mevent->pos()); if (!index.isValid()) return false; /** * We should eat the first event that arrives exactly when * the drop down appears on screen. */ if (!m_buttonPressed) return true; const bool toUncheckedState = index.data(Qt::CheckStateRole) == Qt::Checked; if (toUncheckedState) { m_parent->model()->setData(index, Qt::Unchecked, Qt::CheckStateRole); } else { m_parent->model()->setData(index, Qt::Checked, Qt::CheckStateRole); } if (index.data(OriginalLabelIndex).toInt() == -1) { for (int i = 0; i < m_parent->model()->rowCount(); i++) { const QModelIndex &other = m_parent->model()->index(i, 0); if (other.data(OriginalLabelIndex) != -1) { m_parent->model()->setData(other, toUncheckedState ? Qt::Unchecked : Qt::Checked, Qt::CheckStateRole); } } } else { bool prevChecked = false; bool checkedVaries = false; QModelIndex allLabelsIndex; for (int i = 0; i < m_parent->model()->rowCount(); i++) { const QModelIndex &other = m_parent->model()->index(i, 0); if (other.data(OriginalLabelIndex) != -1) { const bool currentChecked = other.data(Qt::CheckStateRole) == Qt::Checked; if (i == 0) { prevChecked = currentChecked; } else { if (prevChecked != currentChecked) { checkedVaries = true; break; } } } else { allLabelsIndex = other; } } const bool allLabelsIndexShouldBeChecked = prevChecked && !checkedVaries; if (allLabelsIndexShouldBeChecked != (allLabelsIndex.data(Qt::CheckStateRole) == Qt::Checked)) { m_parent->model()->setData(allLabelsIndex, allLabelsIndexShouldBeChecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); } } emit m_parent->selectedColorsChanged(); m_buttonPressed = false; return true; } return QObject::eventFilter(obj, event); } private: KisColorFilterCombo *m_parent; bool m_buttonPressed; }; class FullSizedListView : public QListView { public: QSize sizeHint() const override { return contentsSize(); } }; +class PopupComboBoxStyle : public QProxyStyle +{ +public: + PopupComboBoxStyle(QStyle *baseStyle = nullptr) : QProxyStyle(baseStyle) {} + + int styleHint(QStyle::StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const override + { + // This flag makes ComboBox popup float ontop of its parent ComboBox, like in Fusion style. + // Only when this hint is set will Qt respect combobox popup size hints, otherwise the popup + // can never exceed the width of its parent ComboBox, like in Breeze style. + if (hint == QStyle::SH_ComboBox_Popup) { + return true; + } + + return QProxyStyle::styleHint(hint, option, widget, returnData); + } +}; + struct KisColorFilterCombo::Private { LabelFilteringModel *filteringModel; }; KisColorFilterCombo::KisColorFilterCombo(QWidget *parent) : QComboBox(parent), m_d(new Private) { QStandardItemModel *newModel = new QStandardItemModel(this); setModel(newModel); + PopupComboBoxStyle *proxyStyle = new PopupComboBoxStyle(style()); + proxyStyle->setParent(this); + setStyle(proxyStyle); + setView(new FullSizedListView); m_eventFilters.append(new ComboEventFilter(this)); m_eventFilters.append(new ComboEventFilter(this)); view()->installEventFilter(m_eventFilters[0]); view()->viewport()->installEventFilter(m_eventFilters[1]); KisNodeViewColorScheme scm; QStandardItem* item = new QStandardItem(i18nc("combo box: show all layers", "All")); item->setCheckable(true); item->setCheckState(Qt::Unchecked); item->setData(QColor(Qt::transparent), Qt::BackgroundColorRole); item->setData(int(-1), OriginalLabelIndex); item->setData(QSize(30, scm.rowHeight()), Qt::SizeHintRole); newModel->appendRow(item); int labelIndex = 0; foreach (const QColor &color, scm.allColorLabels()) { const QString title = color.alpha() > 0 ? "" : i18nc("combo box: select all layers without a label", "No Label"); QStandardItem* item = new QStandardItem(title); item->setCheckable(true); item->setCheckState(Qt::Unchecked); item->setData(color, Qt::BackgroundColorRole); item->setData(labelIndex, OriginalLabelIndex); item->setData(QSize(30, scm.rowHeight()), Qt::SizeHintRole); newModel->appendRow(item); labelIndex++; } m_d->filteringModel = new LabelFilteringModel(this); QAbstractItemModel *originalModel = model(); originalModel->setParent(m_d->filteringModel); m_d->filteringModel->setSourceModel(originalModel); setModel(m_d->filteringModel); } KisColorFilterCombo::~KisColorFilterCombo() { qDeleteAll(m_eventFilters); } void collectAvailableLabels(KisNodeSP root, QSet *labels) { labels->insert(root->colorLabelIndex()); KisNodeSP node = root->firstChild(); while (node) { collectAvailableLabels(node, labels); node = node->nextSibling(); } } void KisColorFilterCombo::updateAvailableLabels(KisNodeSP rootNode) { QSet labels; collectAvailableLabels(rootNode, &labels); updateAvailableLabels(labels); } void KisColorFilterCombo::updateAvailableLabels(const QSet &labels) { m_d->filteringModel->setAcceptedLabels(labels); } QList KisColorFilterCombo::selectedColors() const { QList colors; for (int i = 0; i < model()->rowCount(); i++) { const QModelIndex &other = model()->index(i, 0); const int label = other.data(OriginalLabelIndex).toInt(); if (label != -1 && other.data(Qt::CheckStateRole) == Qt::Checked) { colors << label; } } return colors; } void KisColorFilterCombo::paintEvent(QPaintEvent *event) { Q_UNUSED(event); QStylePainter painter(this); painter.setPen(palette().color(QPalette::Text)); // draw the combobox frame, focusrect and selected etc. QStyleOptionComboBox opt; initStyleOption(&opt); painter.drawComplexControl(QStyle::CC_ComboBox, opt); { KisNodeViewColorScheme scm; const QRect editRect = style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField, this); const int size = qMin(editRect.width(), editRect.height()); const QList selectedColors = this->selectedColors(); if (selectedColors.size() == 0 || selectedColors.size() == model()->rowCount() - 1) { QIcon icon = KisIconUtils::loadIcon("view-filter"); QPixmap pixmap = icon.pixmap(QSize(size, size), !isEnabled() ? QIcon::Disabled : QIcon::Normal); painter.drawPixmap(editRect.right() - size, editRect.top(), pixmap); } else if (selectedColors.size() == 1) { const int currentLabel = selectedColors.first(); QColor currentColor = scm.colorLabel(currentLabel); if (currentColor.alpha() > 0) { painter.fillRect(editRect, currentColor); } else if (currentLabel == 0) { QPen oldPen = painter.pen(); const int border = 4; QRect crossRect(0, 0, size - 2 * border, size - 2 * border); crossRect.moveCenter(editRect.center()); QColor shade = opt.palette.dark().color(); painter.setPen(QPen(shade, 2)); painter.drawLine(crossRect.topLeft(), crossRect.bottomRight()); painter.drawLine(crossRect.bottomLeft(), crossRect.topRight()); } } else { const int border = 0; QRect pieRect(0, 0, size - 2 * border, size - 2 * border); pieRect.moveCenter(editRect.center()); const int numColors = selectedColors.size(); const int step = 16 * 360 / numColors; int currentAngle = 0; //painter.save(); // optimize out! painter.setRenderHint(QPainter::Antialiasing); for (int i = 0; i < numColors; i++) { QColor color = scm.colorLabel(selectedColors[i]); QBrush brush = color.alpha() > 0 ? QBrush(color) : QBrush(Qt::black, Qt::Dense4Pattern); painter.setPen(color); painter.setBrush(brush); painter.drawPie(pieRect, currentAngle, step); currentAngle += step; } //painter.restore(); // optimize out! } } // draw the icon and text //painter.drawControl(QStyle::CE_ComboBoxLabel, opt); } QSize KisColorFilterCombo::minimumSizeHint() const { return sizeHint(); } QSize KisColorFilterCombo::sizeHint() const { QStyleOptionComboBox opt; initStyleOption(&opt); const QStyleOption *baseOption = qstyleoption_cast(&opt); const int arrowSize = style()->pixelMetric(QStyle::PM_ScrollBarExtent, baseOption, this); const QSize originalHint = QComboBox::sizeHint(); QSize sh(3 * arrowSize, originalHint.height()); - return sh.expandedTo(QApplication::globalStrut()); + return sh; } diff --git a/libs/ui/widgets/kis_curve_widget.h b/libs/ui/widgets/kis_curve_widget.h index 5ffca8bdf8..5e0c644434 100644 --- a/libs/ui/widgets/kis_curve_widget.h +++ b/libs/ui/widgets/kis_curve_widget.h @@ -1,178 +1,178 @@ /* * 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. */ #ifndef KIS_CURVE_WIDGET_H #define KIS_CURVE_WIDGET_H // Qt includes. #include #include #include #include #include #include #include #include #include #include class QSpinBox; class KisCubicCurve; /** * KisCurveWidget is a widget that shows a single curve that can be edited * by the user. The user can grab the curve and move it; this creates * a new control point. Control points can be deleted by selecting a point * and pressing the delete key. * - * (From: http://techbase.kde.org/Projects/Widgets_and_Classes#KisCurveWidget) + * (From: https://techbase.kde.org/Projects/Widgets_and_Classes#KisCurveWidget) * KisCurveWidget allows editing of spline based y=f(x) curves. Handy for cases * where you want the user to control such things as tablet pressure * response, color transformations, acceleration by time, aeroplane lift *by angle of attack. */ class KRITAUI_EXPORT KisCurveWidget : public QWidget { Q_OBJECT Q_PROPERTY(bool pointSelected READ pointSelected NOTIFY pointSelectedChanged); public: friend class CurveEditorItem; /** * Create a new curve widget with a default curve, that is a straight * line from bottom-left to top-right. */ KisCurveWidget(QWidget *parent = 0, Qt::WindowFlags f = 0); ~KisCurveWidget() override; /** * Reset the curve to the default shape */ void reset(void); /** * Enable the guide and set the guide color to the specified color. * * XXX: it seems that the guide feature isn't actually implemented yet? */ void setCurveGuide(const QColor & color); /** * Set a background pixmap. The background pixmap will be drawn under * the grid and the curve. * * XXX: or is the pixmap what is drawn to the left and bottom of the curve * itself? */ void setPixmap(const QPixmap & pix); QPixmap getPixmap(); void setBasePixmap(const QPixmap & pix); QPixmap getBasePixmap(); /** * Whether or not there is a point selected * This does NOT include the first and last points */ bool pointSelected() const; Q_SIGNALS: /** * Emitted whenever a control point has changed position. */ void modified(void); /** * Emitted whenever the status of whether a control point is selected or not changes */ void pointSelectedChanged(); /** * Emitted to notify that the start() function in compressor can be activated. * Thanks to that, blocking signals in curve widget blocks "sending signals" * (calling start() function) *to* the signal compressor. * It effectively makes signals work nearly the same way they worked before * adding the signal compressor in between. */ void compressorShouldEmitModified(); protected Q_SLOTS: void inOutChanged(int); void notifyModified(); /** * This function is called when compressorShouldEmitModified() is emitted. * For why it's needed, \see compressorShouldEmitModified() */ void slotCompressorShouldEmitModified(); protected: void keyPressEvent(QKeyEvent *) override; void paintEvent(QPaintEvent *) override; void mousePressEvent(QMouseEvent * e) override; void mouseReleaseEvent(QMouseEvent * e) override; void mouseMoveEvent(QMouseEvent * e) override; void leaveEvent(QEvent *) override; void resizeEvent(QResizeEvent *e) override; public: /** * @return get a list with all defined points. If you want to know what the * y value for a given x is on the curve defined by these points, use getCurveValue(). * @see getCurveValue */ KisCubicCurve curve(); /** * Replace the current curve with a curve specified by the curve defined by the control * points in @p inlist. */ void setCurve(KisCubicCurve inlist); /** * Connect/disconnect external spinboxes to the curve * @p inMin / @p inMax - is the range for input values * @p outMin / @p outMax - is the range for output values */ void setupInOutControls(QSpinBox *in, QSpinBox *out, int inMin, int inMax, int outMin, int outMax); void dropInOutControls(); /** * Handy function that creates new point in the middle * of the curve and sets focus on the @p m_intIn field, * so the user can move this point anywhere in a moment */ void addPointInTheMiddle(); private: class Private; Private * const d; }; #endif /* KIS_CURVE_WIDGET_H */ diff --git a/libs/ui/widgets/kis_filter_selector_widget.cc b/libs/ui/widgets/kis_filter_selector_widget.cc index dcf73c9f14..68717dc03d 100644 --- a/libs/ui/widgets/kis_filter_selector_widget.cc +++ b/libs/ui/widgets/kis_filter_selector_widget.cc @@ -1,363 +1,367 @@ /* * Copyright (c) 2007-2008 Cyrille Berger * Copyright (c) 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. */ #include "kis_filter_selector_widget.h" #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgfilterselector.h" #include #include #include #include #include #include "kis_default_bounds.h" #include // From krita/ui #include "kis_bookmarked_configurations_editor.h" #include "kis_bookmarked_filter_configurations_model.h" #include "kis_filters_model.h" #include "kis_config.h" class ThumbnailBounds : public KisDefaultBounds { public: ThumbnailBounds() : KisDefaultBounds() {} ~ThumbnailBounds() override {} QRect bounds() const override { return QRect(0, 0, 100, 100); } private: Q_DISABLE_COPY(ThumbnailBounds) }; struct KisFilterSelectorWidget::Private { QWidget *currentCentralWidget {0}; KisConfigWidget *currentFilterConfigurationWidget {0}; KisFilterSP currentFilter; KisPaintDeviceSP paintDevice; Ui_FilterSelector uiFilterSelector; KisPaintDeviceSP thumb; KisBookmarkedFilterConfigurationsModel *currentBookmarkedFilterConfigurationsModel {0}; KisFiltersModel *filtersModel {}; QGridLayout *widgetLayout {}; KisViewManager *view{}; bool showFilterGallery {true}; bool usedForMask {false}; }; KisFilterSelectorWidget::KisFilterSelectorWidget(QWidget* parent) : d(new Private) { Q_UNUSED(parent); setObjectName("KisFilterSelectorWidget"); d->uiFilterSelector.setupUi(this); d->widgetLayout = new QGridLayout(d->uiFilterSelector.centralWidgetHolder); d->widgetLayout->setContentsMargins(0,0,0,0); d->widgetLayout->setHorizontalSpacing(0); showFilterGallery(false); connect(d->uiFilterSelector.filtersSelector, SIGNAL(clicked(QModelIndex)), SLOT(setFilterIndex(QModelIndex))); connect(d->uiFilterSelector.filtersSelector, SIGNAL(activated(QModelIndex)), SLOT(setFilterIndex(QModelIndex))); connect(d->uiFilterSelector.comboBoxPresets, SIGNAL(activated(int)),SLOT(slotBookmarkedFilterConfigurationSelected(int))); connect(d->uiFilterSelector.pushButtonEditPressets, SIGNAL(pressed()), SLOT(editConfigurations())); connect(d->uiFilterSelector.btnXML, SIGNAL(clicked()), this, SLOT(showXMLdialog())); KisConfig cfg(true); d->uiFilterSelector.chkRememberPreset->setChecked(cfg.readEntry("filterdialog/rememberlastpreset", false)); } KisFilterSelectorWidget::~KisFilterSelectorWidget() { KisConfig cfg(false); cfg.writeEntry("filterdialog/rememberlastpreset", d->uiFilterSelector.chkRememberPreset->isChecked()); delete d->filtersModel; delete d->currentBookmarkedFilterConfigurationsModel; delete d->currentCentralWidget; delete d->widgetLayout; delete d; } void KisFilterSelectorWidget::setView(KisViewManager *view) { d->view = view; } void KisFilterSelectorWidget::setPaintDevice(bool showAll, KisPaintDeviceSP _paintDevice) { if (!_paintDevice) return; if (d->filtersModel) delete d->filtersModel; d->usedForMask = !showAll; d->paintDevice = _paintDevice; d->thumb = d->paintDevice->createThumbnailDevice(100, 100); d->thumb->setDefaultBounds(new ThumbnailBounds()); d->filtersModel = new KisFiltersModel(showAll, d->thumb); d->uiFilterSelector.filtersSelector->setFilterModel(d->filtersModel); d->uiFilterSelector.filtersSelector->header()->setVisible(false); KisConfig cfg(true); QModelIndex idx = d->filtersModel->indexForFilter(cfg.readEntry("FilterSelector/LastUsedFilter", "levels")); if (!idx.isValid()) { idx = d->filtersModel->indexForFilter("levels"); } - if (isFilterGalleryVisible()) { + if (d->usedForMask && isFilterGalleryVisible()) { d->uiFilterSelector.filtersSelector->activateFilter(idx); } } void KisFilterSelectorWidget::showFilterGallery(bool visible) { if (d->showFilterGallery == visible) { return; } d->showFilterGallery = visible; update(); emit sigFilterGalleryToggled(visible); emit sigSizeChanged(); } void KisFilterSelectorWidget::showXMLdialog() { if (currentFilter()->showConfigurationWidget()) { QDialog *xmlDialog = new QDialog(); xmlDialog->setMinimumWidth(500); xmlDialog->setWindowTitle(i18n("Filter configuration XML")); QVBoxLayout *xmllayout = new QVBoxLayout(xmlDialog); QPlainTextEdit *text = new QPlainTextEdit(xmlDialog); KisFilterConfigurationSP config = configuration(); text->setPlainText(config->toXML()); xmllayout->addWidget(text); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, xmlDialog); connect(buttons, SIGNAL(accepted()), xmlDialog, SLOT(accept())); connect(buttons, SIGNAL(rejected()), xmlDialog, SLOT(reject())); xmllayout->addWidget(buttons); if (xmlDialog->exec()==QDialog::Accepted) { QDomDocument doc; doc.setContent(text->toPlainText()); config->fromXML(doc.documentElement()); if (config) { d->currentFilterConfigurationWidget->setConfiguration(config); } } } } bool KisFilterSelectorWidget::isFilterGalleryVisible() const { return d->showFilterGallery; } KisFilterSP KisFilterSelectorWidget::currentFilter() const { return d->currentFilter; } void KisFilterSelectorWidget::setFilter(KisFilterSP f) { Q_ASSERT(f); Q_ASSERT(d->filtersModel); setWindowTitle(f->name()); dbgKrita << "setFilter: " << f; d->currentFilter = f; delete d->currentCentralWidget; { bool v = d->uiFilterSelector.filtersSelector->blockSignals(true); d->uiFilterSelector.filtersSelector->setCurrentIndex(d->filtersModel->indexForFilter(f->id())); d->uiFilterSelector.filtersSelector->blockSignals(v); } KisConfigWidget* widget = d->currentFilter->createConfigurationWidget(d->uiFilterSelector.centralWidgetHolder, d->paintDevice, d->usedForMask); if (!widget) { // No widget, so display a label instead d->uiFilterSelector.comboBoxPresets->setEnabled(false); d->uiFilterSelector.pushButtonEditPressets->setEnabled(false); d->uiFilterSelector.btnXML->setEnabled(false); d->currentFilterConfigurationWidget = 0; d->currentCentralWidget = new QLabel(i18n("No configuration options"), d->uiFilterSelector.centralWidgetHolder); d->uiFilterSelector.scrollArea->setMinimumSize(d->currentCentralWidget->sizeHint()); qobject_cast(d->currentCentralWidget)->setAlignment(Qt::AlignCenter); } else { d->uiFilterSelector.comboBoxPresets->setEnabled(true); d->uiFilterSelector.pushButtonEditPressets->setEnabled(true); d->uiFilterSelector.btnXML->setEnabled(true); d->currentFilterConfigurationWidget = widget; d->currentCentralWidget = widget; widget->layout()->setContentsMargins(0,0,0,0); d->currentFilterConfigurationWidget->setView(d->view); d->currentFilterConfigurationWidget->blockSignals(true); d->currentFilterConfigurationWidget->setConfiguration(d->currentFilter->defaultConfiguration()); d->currentFilterConfigurationWidget->blockSignals(false); d->uiFilterSelector.scrollArea->setContentsMargins(0,0,0,0); d->uiFilterSelector.scrollArea->setMinimumWidth(widget->sizeHint().width() + 18); connect(d->currentFilterConfigurationWidget, SIGNAL(sigConfigurationUpdated()), this, SIGNAL(configurationChanged())); } // Change the list of presets delete d->currentBookmarkedFilterConfigurationsModel; d->currentBookmarkedFilterConfigurationsModel = new KisBookmarkedFilterConfigurationsModel(d->thumb, f); d->uiFilterSelector.comboBoxPresets->setModel(d->currentBookmarkedFilterConfigurationsModel); // Add the widget to the layout d->currentCentralWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); d->widgetLayout->addWidget(d->currentCentralWidget, 0 , 0); if (d->uiFilterSelector.chkRememberPreset->isChecked()) { int lastBookmarkedFilterConfiguration = KisConfig(true).readEntry("lastBookmarkedFilterConfiguration/" + f->id(), 0); if (d->uiFilterSelector.comboBoxPresets->count() > lastBookmarkedFilterConfiguration) { d->uiFilterSelector.comboBoxPresets->setCurrentIndex(lastBookmarkedFilterConfiguration); slotBookmarkedFilterConfigurationSelected(lastBookmarkedFilterConfiguration); } } update(); } void KisFilterSelectorWidget::setFilterIndex(const QModelIndex& idx) { if (!idx.isValid()) return; Q_ASSERT(d->filtersModel); KisFilter* filter = const_cast(d->filtersModel->indexToFilter(idx)); if (filter) { setFilter(filter); } else { if (d->currentFilter) { bool v = d->uiFilterSelector.filtersSelector->blockSignals(true); QModelIndex idx = d->filtersModel->indexForFilter(d->currentFilter->id()); d->uiFilterSelector.filtersSelector->setCurrentIndex(idx); d->uiFilterSelector.filtersSelector->scrollTo(idx); d->uiFilterSelector.filtersSelector->blockSignals(v); } } + slotBookMarkCurrentFilter(); + emit(configurationChanged()); + } + + void KisFilterSelectorWidget::slotBookMarkCurrentFilter() { KisConfig cfg(false); cfg.writeEntry("FilterSelector/LastUsedFilter", d->currentFilter->id()); - emit(configurationChanged()); } void KisFilterSelectorWidget::slotBookmarkedFilterConfigurationSelected(int index) { if (d->currentFilterConfigurationWidget) { QModelIndex modelIndex = d->currentBookmarkedFilterConfigurationsModel->index(index, 0); KisFilterConfigurationSP config = d->currentBookmarkedFilterConfigurationsModel->configuration(modelIndex); d->currentFilterConfigurationWidget->setConfiguration(config); if (d->currentFilter && index != KisConfig(true).readEntry("lastBookmarkedFilterConfiguration/" + d->currentFilter->id(), 0)) { KisConfig(false).writeEntry("lastBookmarkedFilterConfiguration/" + d->currentFilter->id(), index); } } } void KisFilterSelectorWidget::editConfigurations() { KisSerializableConfigurationSP config = d->currentFilterConfigurationWidget ? d->currentFilterConfigurationWidget->configuration() : 0; KisBookmarkedConfigurationsEditor editor(this, d->currentBookmarkedFilterConfigurationsModel, config); editor.exec(); } void KisFilterSelectorWidget::update() { d->uiFilterSelector.filtersSelector->setVisible(d->showFilterGallery); if (d->showFilterGallery) { setMinimumWidth(qMax(sizeHint().width(), 700)); d->uiFilterSelector.scrollArea->setMinimumHeight(400); setMinimumHeight(d->uiFilterSelector.verticalLayout->sizeHint().height()); if (d->currentFilter) { bool v = d->uiFilterSelector.filtersSelector->blockSignals(true); d->uiFilterSelector.filtersSelector->setCurrentIndex(d->filtersModel->indexForFilter(d->currentFilter->id())); d->uiFilterSelector.filtersSelector->blockSignals(v); } } else { if (d->currentCentralWidget) { d->uiFilterSelector.scrollArea->setMinimumHeight(qMin(400, d->currentCentralWidget->sizeHint().height())); } setMinimumSize(d->uiFilterSelector.verticalLayout->sizeHint()); } } KisFilterConfigurationSP KisFilterSelectorWidget::configuration() { if (d->currentFilterConfigurationWidget) { KisFilterConfigurationSP config = dynamic_cast(d->currentFilterConfigurationWidget->configuration().data()); if (config) { return config; } } else if (d->currentFilter) { return d->currentFilter->defaultConfiguration(); } return 0; } void KisFilterTree::setFilterModel(QAbstractItemModel *model) { m_model = model; } void KisFilterTree::activateFilter(QModelIndex idx) { setModel(m_model); selectionModel()->select(idx, QItemSelectionModel::SelectCurrent); expand(idx); scrollTo(idx); emit activated(idx); } void KisFilterSelectorWidget::setVisible(bool visible) { QWidget::setVisible(visible); if (visible) { update(); } } diff --git a/libs/ui/widgets/kis_filter_selector_widget.h b/libs/ui/widgets/kis_filter_selector_widget.h index d09cb19161..a8dd40a09d 100644 --- a/libs/ui/widgets/kis_filter_selector_widget.h +++ b/libs/ui/widgets/kis_filter_selector_widget.h @@ -1,153 +1,154 @@ /* * Copyright (c) 2007-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. */ #ifndef _KIS_FILTER_SELECTOR_WIDGET_H_ #define _KIS_FILTER_SELECTOR_WIDGET_H_ #include #include #include #include #include #include #include #include class QModelIndex; class KisFilterConfiguration; class KisViewManager; class QAbstractItemModel; class QHideEvent; class QShowEvent; /** * Widget for selecting the filter. This shows the widget if there is any. */ class KisFilterSelectorWidget : public QWidget { Q_OBJECT public: KisFilterSelectorWidget(QWidget* parent); ~KisFilterSelectorWidget() override; void setFilter(KisFilterSP f); void setView(KisViewManager *view); void setPaintDevice(bool showAll, KisPaintDeviceSP); KisFilterConfigurationSP configuration(); bool isFilterGalleryVisible() const; KisFilterSP currentFilter() const; public Q_SLOTS: void setVisible(bool visible) override; void showFilterGallery(bool visible); protected Q_SLOTS: void slotBookmarkedFilterConfigurationSelected(int); + void slotBookMarkCurrentFilter(); void setFilterIndex(const QModelIndex&); void editConfigurations(); void update(); void showXMLdialog(); Q_SIGNALS: void configurationChanged(); void sigFilterGalleryToggled(bool visible); void sigSizeChanged(); private: struct Private; Private* const d; }; class KisFilterTree: public QTreeView { Q_OBJECT public: KisFilterTree(QWidget *parent) : QTreeView(parent) { QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(this); if (scroller) { connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State))); } connect(this, SIGNAL(expanded(QModelIndex)), this, SLOT(update_scroll_area(QModelIndex))); connect(this, SIGNAL(collapsed(QModelIndex)), this, SLOT(update_scroll_area(QModelIndex))); } void setFilterModel(QAbstractItemModel * model); void activateFilter(QModelIndex idx); QSize minimumSizeHint() const override { return QSize(200, QTreeView::sizeHint().height()); } QSize sizeHint() const override { return QSize(header()->width(), QTreeView::sizeHint().height()); } void setModel(QAbstractItemModel *model) override { QTreeView::setModel(model); if (header()->visualIndex(0) != -1) { header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); } } protected: void resizeEvent(QResizeEvent *event) override { if (event->size().width() > 10) { setModel(m_model); } else { setModel(0); } QTreeView::resizeEvent(event); } void showEvent(QShowEvent * event) override { setModel(m_model); QTreeView::showEvent(event); } void hideEvent(QHideEvent * event) override { setModel(0); QTreeView::hideEvent(event); } private Q_SLOTS: void update_scroll_area(const QModelIndex& i) { resizeColumnToContents(i.column()); } public Q_SLOTS: void slotScrollerStateChanged(QScroller::State state){ KisKineticScroller::updateCursor(this, state); } private: QAbstractItemModel *m_model; }; #endif diff --git a/libs/ui/widgets/kis_slider_spin_box.cpp b/libs/ui/widgets/kis_slider_spin_box.cpp index 8db1984887..5e36564366 100644 --- a/libs/ui/widgets/kis_slider_spin_box.cpp +++ b/libs/ui/widgets/kis_slider_spin_box.cpp @@ -1,1052 +1,1052 @@ /* This file is part of the KDE project * Copyright (c) 2010 Justin Noel * Copyright (c) 2010 Cyrille Berger * Copyright (c) 2015 Moritz Molch * * 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_slider_spin_box.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_cursor.h" #include "KisPart.h" #include "input/kis_input_manager.h" #include "kis_num_parser.h" class KisAbstractSliderSpinBoxPrivate { public: enum Style { STYLE_NOQUIRK, STYLE_PLASTIQUE, STYLE_BREEZE, STYLE_FUSION, }; QLineEdit* edit; QDoubleValidator* validator; bool upButtonDown; bool downButtonDown; int factor; int fastSliderStep; qreal slowFactor; qreal shiftPercent; bool shiftMode; QString prefix; QString suffix; qreal exponentRatio; int value; int maximum; int minimum; int singleStep; QSpinBox* dummySpinBox; Style style; bool blockUpdateSignalOnDrag; bool isDragging; bool parseInt; }; KisAbstractSliderSpinBox::KisAbstractSliderSpinBox(QWidget* parent, KisAbstractSliderSpinBoxPrivate* _d) : QWidget(parent) , d_ptr(_d) { Q_D(KisAbstractSliderSpinBox); QEvent e(QEvent::StyleChange); changeEvent(&e); d->upButtonDown = false; d->downButtonDown = false; d->edit = new QLineEdit(this); d->edit->setFrame(false); d->edit->setAlignment(Qt::AlignCenter); d->edit->hide(); d->edit->setContentsMargins(0,0,0,0); d->edit->installEventFilter(this); //Make edit transparent d->edit->setAutoFillBackground(false); QPalette pal = d->edit->palette(); pal.setColor(QPalette::Base, Qt::transparent); d->edit->setPalette(pal); d->edit->setContextMenuPolicy(Qt::PreventContextMenu); connect(d->edit, SIGNAL(editingFinished()), this, SLOT(editLostFocus())); d->validator = new QDoubleValidator(d->edit); d->value = 0; d->minimum = 0; d->maximum = 100; d->factor = 1.0; d->singleStep = 1; d->fastSliderStep = 5; d->slowFactor = 0.1; d->shiftMode = false; d->blockUpdateSignalOnDrag = false; d->isDragging = false; d->parseInt = false; setExponentRatio(1.0); setCursor(KisCursor::splitHCursor()); //Set sane defaults setFocusPolicy(Qt::StrongFocus); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); //dummy needed to fix a bug in the polyester theme d->dummySpinBox = new QSpinBox(this); d->dummySpinBox->hide(); } KisAbstractSliderSpinBox::~KisAbstractSliderSpinBox() { Q_D(KisAbstractSliderSpinBox); delete d; } void KisAbstractSliderSpinBox::showEdit() { Q_D(KisAbstractSliderSpinBox); if (d->edit->isVisible()) return; if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE) { d->edit->setGeometry(progressRect(spinBoxOptions()).adjusted(0,0,-2,0)); } else { d->edit->setGeometry(progressRect(spinBoxOptions())); } d->edit->setText(valueString()); d->edit->selectAll(); d->edit->show(); d->edit->setFocus(Qt::OtherFocusReason); update(); } void KisAbstractSliderSpinBox::hideEdit() { Q_D(KisAbstractSliderSpinBox); d->edit->hide(); update(); } void KisAbstractSliderSpinBox::paintEvent(QPaintEvent* e) { Q_D(KisAbstractSliderSpinBox); Q_UNUSED(e) QPainter painter(this); switch (d->style) { case KisAbstractSliderSpinBoxPrivate::STYLE_FUSION: paintFusion(painter); break; case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE: paintPlastique(painter); break; case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE: paintBreeze(painter); break; default: paint(painter); break; } painter.end(); } void KisAbstractSliderSpinBox::paint(QPainter &painter) { Q_D(KisAbstractSliderSpinBox); //Create options to draw spin box parts QStyleOptionSpinBox spinOpts = spinBoxOptions(); spinOpts.rect.adjust(0, 2, 0, -2); //Draw "SpinBox".Clip off the area of the lineEdit to avoid double //borders being drawn painter.save(); painter.setClipping(true); QRect eraseRect(QPoint(rect().x(), rect().y()), QPoint(progressRect(spinOpts).right(), rect().bottom())); painter.setClipRegion(QRegion(rect()).subtracted(eraseRect)); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); painter.setClipping(false); painter.restore(); QStyleOptionProgressBar progressOpts = progressBarOptions(); progressOpts.rect.adjust(0, 2, 0, -2); style()->drawControl(QStyle::CE_ProgressBar, &progressOpts, &painter, 0); //Draw focus if necessary if (hasFocus() && d->edit->hasFocus()) { QStyleOptionFocusRect focusOpts; focusOpts.initFrom(this); focusOpts.rect = progressOpts.rect; focusOpts.backgroundColor = palette().color(QPalette::Window); style()->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpts, &painter, this); } } void KisAbstractSliderSpinBox::paintFusion(QPainter &painter) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); spinOpts.frame = true; spinOpts.rect.adjust(0, -1, 0, 1); //spinOpts.palette().setBrush(QPalette::Base, palette().highlight()); QStyleOptionProgressBar progressOpts = progressBarOptions(); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); painter.save(); QRect rect = progressOpts.rect.adjusted(1,2,-4,-2); QRect leftRect; int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0), qreal(progressOpts.maximum) - progressOpts.minimum) * rect.width(); if (progressIndicatorPos >= 0 && progressIndicatorPos <= rect.width()) { leftRect = QRect(rect.left(), rect.top(), progressIndicatorPos, rect.height()); } else if (progressIndicatorPos > rect.width()) { painter.setPen(palette().highlightedText().color()); } else { painter.setPen(palette().buttonText().color()); } QRegion rightRect = rect; rightRect = rightRect.subtracted(leftRect); QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter); textOption.setWrapMode(QTextOption::NoWrap); if (!(d->edit && d->edit->isVisible())) { painter.setClipRegion(rightRect); painter.setClipping(true); painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption); painter.setClipping(false); } if (!leftRect.isNull()) { painter.setClipRect(leftRect.adjusted(0, -1, 1, 1)); painter.setPen(palette().highlight().color()); painter.setBrush(palette().highlight()); spinOpts.palette.setBrush(QPalette::Base, palette().highlight()); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); if (!(d->edit && d->edit->isVisible())) { painter.setPen(palette().highlightedText().color()); painter.setClipping(true); painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption); } painter.setClipping(false); } painter.restore(); } void KisAbstractSliderSpinBox::paintPlastique(QPainter &painter) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QStyleOptionProgressBar progressOpts = progressBarOptions(); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); painter.save(); QRect rect = progressOpts.rect.adjusted(2,0,-2,0); QRect leftRect; int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0), qreal(progressOpts.maximum) - progressOpts.minimum) * rect.width(); if (progressIndicatorPos >= 0 && progressIndicatorPos <= rect.width()) { leftRect = QRect(rect.left(), rect.top(), progressIndicatorPos, rect.height()); } else if (progressIndicatorPos > rect.width()) { painter.setPen(palette().highlightedText().color()); } else { painter.setPen(palette().buttonText().color()); } QRegion rightRect = rect; rightRect = rightRect.subtracted(leftRect); QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter); textOption.setWrapMode(QTextOption::NoWrap); if (!(d->edit && d->edit->isVisible())) { painter.setClipRegion(rightRect); painter.setClipping(true); painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption); painter.setClipping(false); } if (!leftRect.isNull()) { painter.setPen(palette().highlight().color()); painter.setBrush(palette().highlight()); painter.drawRect(leftRect.adjusted(0,0,0,-1)); if (!(d->edit && d->edit->isVisible())) { painter.setPen(palette().highlightedText().color()); painter.setClipRect(leftRect.adjusted(0,0,1,0)); painter.setClipping(true); painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption); painter.setClipping(false); } } painter.restore(); } void KisAbstractSliderSpinBox::paintBreeze(QPainter &painter) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QStyleOptionProgressBar progressOpts = progressBarOptions(); QString valueText = progressOpts.text; progressOpts.text = ""; progressOpts.rect.adjust(0, 1, 0, -1); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, this); style()->drawControl(QStyle::CE_ProgressBarGroove, &progressOpts, &painter, this); painter.save(); QRect leftRect; int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0), qreal(progressOpts.maximum) - progressOpts.minimum) * progressOpts.rect.width(); if (progressIndicatorPos >= 0 && progressIndicatorPos <= progressOpts.rect.width()) { leftRect = QRect(progressOpts.rect.left(), progressOpts.rect.top(), progressIndicatorPos, progressOpts.rect.height()); } else if (progressIndicatorPos > progressOpts.rect.width()) { painter.setPen(palette().highlightedText().color()); } else { painter.setPen(palette().buttonText().color()); } QRegion rightRect = progressOpts.rect; rightRect = rightRect.subtracted(leftRect); painter.setClipRegion(rightRect); QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter); textOption.setWrapMode(QTextOption::NoWrap); if (!(d->edit && d->edit->isVisible())) { painter.drawText(progressOpts.rect, valueText, textOption); } if (!leftRect.isNull()) { painter.setPen(palette().highlightedText().color()); painter.setClipRect(leftRect); style()->drawControl(QStyle::CE_ProgressBarContents, &progressOpts, &painter, this); if (!(d->edit && d->edit->isVisible())) { painter.drawText(progressOpts.rect, valueText, textOption); } } painter.restore(); } void KisAbstractSliderSpinBox::mousePressEvent(QMouseEvent* e) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); //Depress buttons or highlight slider //Also used to emulate mouse grab... if (e->buttons() & Qt::LeftButton) { if (upButtonRect(spinOpts).contains(e->pos())) { d->upButtonDown = true; } else if (downButtonRect(spinOpts).contains(e->pos())) { d->downButtonDown = true; } } else if (e->buttons() & Qt::RightButton) { showEdit(); } update(); } void KisAbstractSliderSpinBox::mouseReleaseEvent(QMouseEvent* e) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); d->isDragging = false; //Step up/down for buttons //Emualting mouse grab too if (upButtonRect(spinOpts).contains(e->pos()) && d->upButtonDown) { setInternalValue(d->value + d->singleStep); } else if (downButtonRect(spinOpts).contains(e->pos()) && d->downButtonDown) { setInternalValue(d->value - d->singleStep); } else if (progressRect(spinOpts).contains(e->pos()) && !(d->edit->isVisible()) && !(d->upButtonDown || d->downButtonDown)) { //Snap to percentage for progress area setInternalValue(valueForX(e->pos().x(),e->modifiers())); } else { // Confirm the last known value, since we might be ignoring move events setInternalValue(d->value); } d->upButtonDown = false; d->downButtonDown = false; update(); } void KisAbstractSliderSpinBox::mouseMoveEvent(QMouseEvent* e) { Q_D(KisAbstractSliderSpinBox); if( e->modifiers() & Qt::ShiftModifier ) { if( !d->shiftMode ) { d->shiftPercent = pow( qreal(d->value - d->minimum)/qreal(d->maximum - d->minimum), 1/qreal(d->exponentRatio) ); d->shiftMode = true; } } else { d->shiftMode = false; } //Respect emulated mouse grab. if (e->buttons() & Qt::LeftButton && !(d->downButtonDown || d->upButtonDown)) { d->isDragging = true; setInternalValue(valueForX(e->pos().x(),e->modifiers()), d->blockUpdateSignalOnDrag); update(); } } void KisAbstractSliderSpinBox::keyPressEvent(QKeyEvent* e) { Q_D(KisAbstractSliderSpinBox); switch (e->key()) { case Qt::Key_Up: case Qt::Key_Right: setInternalValue(d->value + d->singleStep); break; case Qt::Key_Down: case Qt::Key_Left: setInternalValue(d->value - d->singleStep); break; case Qt::Key_Shift: d->shiftPercent = pow( qreal(d->value - d->minimum)/qreal(d->maximum - d->minimum), 1/qreal(d->exponentRatio) ); d->shiftMode = true; break; case Qt::Key_Enter: //Line edit isn't "accepting" key strokes... case Qt::Key_Return: case Qt::Key_Escape: case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_AltGr: case Qt::Key_Super_L: case Qt::Key_Super_R: break; default: showEdit(); d->edit->event(e); break; } } void KisAbstractSliderSpinBox::wheelEvent(QWheelEvent *e) { Q_D(KisAbstractSliderSpinBox); if ( e->delta() > 0) { setInternalValue(d->value + d->singleStep); } else { setInternalValue(d->value - d->singleStep); } update(); e->accept(); } bool KisAbstractSliderSpinBox::event(QEvent *event) { if (event->type() == QEvent::ShortcutOverride){ QKeyEvent* key = static_cast(event); if (key->modifiers() == Qt::NoModifier){ switch(key->key()){ case Qt::Key_Up: case Qt::Key_Right: case Qt::Key_Down: case Qt::Key_Left: event->accept(); return true; default: break; } } } return QWidget::event(event); } void KisAbstractSliderSpinBox::commitEnteredValue() { Q_D(KisAbstractSliderSpinBox); //QLocale locale; bool ok = false; //qreal value = locale.toDouble(d->edit->text(), &ok) * d->factor; qreal value; if (d->parseInt) { value = KisNumericParser::parseIntegerMathExpr(d->edit->text(), &ok) * d->factor; } else { value = KisNumericParser::parseSimpleMathExpr(d->edit->text(), &ok) * d->factor; } if (ok) { setInternalValue(value); } } bool KisAbstractSliderSpinBox::eventFilter(QObject* recv, QEvent* e) { Q_D(KisAbstractSliderSpinBox); if (recv == static_cast(d->edit) && e->type() == QEvent::KeyRelease) { QKeyEvent* keyEvent = static_cast(e); switch (keyEvent->key()) { case Qt::Key_Enter: case Qt::Key_Return: { commitEnteredValue(); hideEdit(); return true; } case Qt::Key_Escape: d->edit->setText(valueString()); hideEdit(); return true; default: break; } } return false; } QSize KisAbstractSliderSpinBox::sizeHint() const { const Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QFont ft(font()); if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK) { // Some styles use bold font in progressbars // unfortunately there is no reliable way to check for that ft.setBold(true); } QFontMetrics fm(ft); QSize hint(fm.boundingRect(d->prefix + QString::number(d->maximum) + d->suffix).size()); hint += QSize(0, 2); switch (d->style) { case KisAbstractSliderSpinBoxPrivate::STYLE_FUSION: hint += QSize(8, 8); break; case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE: hint += QSize(8, 0); break; case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE: hint += QSize(2, 0); break; case KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK: // almost all "modern" styles have a margin around controls hint += QSize(6, 6); break; default: break; } //Getting the size of the buttons is a pain as the calcs require a rect //that is "big enough". We run the calc twice to get the "smallest" buttons //This code was inspired by QAbstractSpinBox QSize extra(1000, 0); spinOpts.rect.setSize(hint + extra); extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts, QStyle::SC_SpinBoxEditField, this).size(); spinOpts.rect.setSize(hint + extra); extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts, QStyle::SC_SpinBoxEditField, this).size(); hint += extra; spinOpts.rect.setSize(hint); return style()->sizeFromContents(QStyle::CT_SpinBox, &spinOpts, hint) .expandedTo(QApplication::globalStrut()); } QSize KisAbstractSliderSpinBox::minimumSizeHint() const { return sizeHint(); } QSize KisAbstractSliderSpinBox::minimumSize() const { - return QWidget::minimumSize().expandedTo(minimumSizeHint()); + return QWidget::minimumSize(); } QStyleOptionSpinBox KisAbstractSliderSpinBox::spinBoxOptions() const { const Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox opts; opts.initFrom(this); opts.frame = false; opts.buttonSymbols = QAbstractSpinBox::UpDownArrows; opts.subControls = QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown; //Disable non-logical buttons if (d->value == d->minimum) { opts.stepEnabled = QAbstractSpinBox::StepUpEnabled; } else if (d->value == d->maximum) { opts.stepEnabled = QAbstractSpinBox::StepDownEnabled; } else { opts.stepEnabled = QAbstractSpinBox::StepUpEnabled | QAbstractSpinBox::StepDownEnabled; } //Deal with depressed buttons if (d->upButtonDown) { opts.activeSubControls = QStyle::SC_SpinBoxUp; } else if (d->downButtonDown) { opts.activeSubControls = QStyle::SC_SpinBoxDown; } else { opts.activeSubControls = 0; } return opts; } QStyleOptionProgressBar KisAbstractSliderSpinBox::progressBarOptions() const { const Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); //Create opts for drawing the progress portion QStyleOptionProgressBar progressOpts; progressOpts.initFrom(this); progressOpts.maximum = d->maximum; progressOpts.minimum = d->minimum; qreal minDbl = d->minimum; qreal dValues = (d->maximum - minDbl); progressOpts.progress = dValues * pow((d->value - minDbl) / dValues, 1.0 / d->exponentRatio) + minDbl; progressOpts.text = d->prefix + valueString() + d->suffix; progressOpts.textAlignment = Qt::AlignCenter; progressOpts.textVisible = !(d->edit->isVisible()); //Change opts rect to be only the ComboBox's text area progressOpts.rect = progressRect(spinOpts); return progressOpts; } QRect KisAbstractSliderSpinBox::progressRect(const QStyleOptionSpinBox& spinBoxOptions) const { const Q_D(KisAbstractSliderSpinBox); QRect ret = style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxEditField); switch (d->style) { case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE: ret.adjust(-2, 0, 1, 0); break; case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE: ret.adjust(1, 0, 0, 0); break; default: break; } return ret; } QRect KisAbstractSliderSpinBox::upButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const { return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxUp); } QRect KisAbstractSliderSpinBox::downButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const { return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxDown); } int KisAbstractSliderSpinBox::valueForX(int x, Qt::KeyboardModifiers modifiers) const { const Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QRect correctedProgRect; if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_FUSION) { correctedProgRect = progressRect(spinOpts).adjusted(2, 0, -2, 0); } else if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE) { correctedProgRect = progressRect(spinOpts); } else { //Adjust for magic number in style code (margins) correctedProgRect = progressRect(spinOpts).adjusted(2, 2, -2, -2); } //Compute the distance of the progress bar, in pixel qreal leftDbl = correctedProgRect.left(); qreal xDbl = x - leftDbl; //Compute the ration of the progress bar used, linearly (ignoring the exponent) qreal rightDbl = correctedProgRect.right(); qreal minDbl = d->minimum; qreal maxDbl = d->maximum; qreal dValues = (maxDbl - minDbl); qreal percent = (xDbl / (rightDbl - leftDbl)); //If SHIFT is pressed, movement should be slowed. if( modifiers & Qt::ShiftModifier ) { percent = d->shiftPercent + ( percent - d->shiftPercent ) * d->slowFactor; } //Final value qreal exp_percent = pow(percent, d->exponentRatio); qreal realvalue = ((dValues * (percent * exp_percent >= 0 ? exp_percent : -exp_percent)) + minDbl); //If key CTRL is pressed, round to the closest step. if( modifiers & Qt::ControlModifier ) { qreal fstep = d->fastSliderStep; if( modifiers & Qt::ShiftModifier ) { fstep*=d->slowFactor; } realvalue = floor( (realvalue+fstep/2) / fstep ) * fstep; } //Return the value return int(realvalue); } void KisAbstractSliderSpinBox::setPrefix(const QString& prefix) { Q_D(KisAbstractSliderSpinBox); d->prefix = prefix; } void KisAbstractSliderSpinBox::setSuffix(const QString& suffix) { Q_D(KisAbstractSliderSpinBox); d->suffix = suffix; } void KisAbstractSliderSpinBox::setExponentRatio(qreal dbl) { Q_D(KisAbstractSliderSpinBox); Q_ASSERT(dbl > 0); d->exponentRatio = dbl; } void KisAbstractSliderSpinBox::setBlockUpdateSignalOnDrag(bool blockUpdateSignal) { Q_D(KisAbstractSliderSpinBox); d->blockUpdateSignalOnDrag = blockUpdateSignal; } void KisAbstractSliderSpinBox::contextMenuEvent(QContextMenuEvent* event) { event->accept(); } void KisAbstractSliderSpinBox::editLostFocus() { Q_D(KisAbstractSliderSpinBox); if (!d->edit->hasFocus()) { commitEnteredValue(); hideEdit(); } } void KisAbstractSliderSpinBox::setInternalValue(int value) { setInternalValue(value, false); } bool KisAbstractSliderSpinBox::isDragging() const { Q_D(const KisAbstractSliderSpinBox); return d->isDragging; } void KisAbstractSliderSpinBox::setPrivateValue(int value) { Q_D(KisAbstractSliderSpinBox); d->value = qBound(d->minimum, value, d->maximum); } class KisSliderSpinBoxPrivate : public KisAbstractSliderSpinBoxPrivate { }; KisSliderSpinBox::KisSliderSpinBox(QWidget* parent) : KisAbstractSliderSpinBox(parent, new KisSliderSpinBoxPrivate) { Q_D(KisSliderSpinBox); d->parseInt = true; setRange(0,99); } KisSliderSpinBox::~KisSliderSpinBox() { } void KisSliderSpinBox::setRange(int minimum, int maximum) { Q_D(KisSliderSpinBox); d->minimum = minimum; d->maximum = maximum; d->fastSliderStep = (maximum-minimum+1)/20; d->validator->setRange(minimum, maximum, 0); update(); } int KisSliderSpinBox::minimum() const { const Q_D(KisSliderSpinBox); return d->minimum; } void KisSliderSpinBox::setMinimum(int minimum) { Q_D(KisSliderSpinBox); setRange(minimum, d->maximum); } int KisSliderSpinBox::maximum() const { const Q_D(KisSliderSpinBox); return d->maximum; } void KisSliderSpinBox::setMaximum(int maximum) { Q_D(KisSliderSpinBox); setRange(d->minimum, maximum); } int KisSliderSpinBox::fastSliderStep() const { const Q_D(KisSliderSpinBox); return d->fastSliderStep; } void KisSliderSpinBox::setFastSliderStep(int step) { Q_D(KisSliderSpinBox); d->fastSliderStep = step; } int KisSliderSpinBox::value() { Q_D(KisSliderSpinBox); return d->value; } void KisSliderSpinBox::setValue(int value) { setInternalValue(value, false); update(); } QString KisSliderSpinBox::valueString() const { const Q_D(KisSliderSpinBox); QLocale locale; return locale.toString((qreal)d->value, 'f', d->validator->decimals()); } void KisSliderSpinBox::setSingleStep(int value) { Q_D(KisSliderSpinBox); d->singleStep = value; } void KisSliderSpinBox::setPageStep(int value) { Q_UNUSED(value); } void KisSliderSpinBox::setInternalValue(int _value, bool blockUpdateSignal) { Q_D(KisAbstractSliderSpinBox); d->value = qBound(d->minimum, _value, d->maximum); if(!blockUpdateSignal) { emit(valueChanged(value())); } } class KisDoubleSliderSpinBoxPrivate : public KisAbstractSliderSpinBoxPrivate { }; KisDoubleSliderSpinBox::KisDoubleSliderSpinBox(QWidget* parent) : KisAbstractSliderSpinBox(parent, new KisDoubleSliderSpinBoxPrivate) { Q_D(KisDoubleSliderSpinBox); d->parseInt = false; } KisDoubleSliderSpinBox::~KisDoubleSliderSpinBox() { } void KisDoubleSliderSpinBox::setRange(qreal minimum, qreal maximum, int decimals) { Q_D(KisDoubleSliderSpinBox); d->factor = pow(10.0, decimals); d->minimum = minimum * d->factor; d->maximum = maximum * d->factor; //This code auto-compute a new step when pressing control. //A flag defaulting to "do not change the fast step" should be added, but it implies changing every call if(maximum - minimum >= 2.0 || decimals <= 0) { //Quick step on integers d->fastSliderStep = int(pow(10.0, decimals)); } else if(decimals == 1) { d->fastSliderStep = (maximum-minimum)*d->factor/10; } else { d->fastSliderStep = (maximum-minimum)*d->factor/20; } d->validator->setRange(minimum, maximum, decimals); update(); setValue(value()); } qreal KisDoubleSliderSpinBox::minimum() const { const Q_D(KisAbstractSliderSpinBox); return d->minimum / d->factor; } void KisDoubleSliderSpinBox::setMinimum(qreal minimum) { Q_D(KisAbstractSliderSpinBox); setRange(minimum, d->maximum); } qreal KisDoubleSliderSpinBox::maximum() const { const Q_D(KisAbstractSliderSpinBox); return d->maximum / d->factor; } void KisDoubleSliderSpinBox::setMaximum(qreal maximum) { Q_D(KisAbstractSliderSpinBox); setRange(d->minimum, maximum); } qreal KisDoubleSliderSpinBox::fastSliderStep() const { const Q_D(KisAbstractSliderSpinBox); return d->fastSliderStep; } void KisDoubleSliderSpinBox::setFastSliderStep(qreal step) { Q_D(KisAbstractSliderSpinBox); d->fastSliderStep = step; } qreal KisDoubleSliderSpinBox::value() { Q_D(KisAbstractSliderSpinBox); return (qreal)d->value / d->factor; } void KisDoubleSliderSpinBox::setValue(qreal value) { Q_D(KisAbstractSliderSpinBox); setInternalValue(d->value = qRound(value * d->factor), false); update(); } void KisDoubleSliderSpinBox::setSingleStep(qreal value) { Q_D(KisAbstractSliderSpinBox); d->singleStep = value * d->factor; } QString KisDoubleSliderSpinBox::valueString() const { const Q_D(KisAbstractSliderSpinBox); QLocale locale; return locale.toString((qreal)d->value / d->factor, 'f', d->validator->decimals()); } void KisDoubleSliderSpinBox::setInternalValue(int _value, bool blockUpdateSignal) { Q_D(KisAbstractSliderSpinBox); d->value = qBound(d->minimum, _value, d->maximum); if(!blockUpdateSignal) { emit(valueChanged(value())); } } void KisAbstractSliderSpinBox::changeEvent(QEvent *e) { Q_D(KisAbstractSliderSpinBox); QWidget::changeEvent(e); switch (e->type()) { case QEvent::StyleChange: if (style()->objectName() == "fusion") { d->style = KisAbstractSliderSpinBoxPrivate::STYLE_FUSION; } else if (style()->objectName() == "plastique") { d->style = KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE; } else if (style()->objectName() == "breeze") { d->style = KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE; } else { d->style = KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK; } break; default: break; } } diff --git a/libs/ui/widgets/kis_stopgradient_slider_widget.cpp b/libs/ui/widgets/kis_stopgradient_slider_widget.cpp index df43af3e4b..210486ab72 100644 --- a/libs/ui/widgets/kis_stopgradient_slider_widget.cpp +++ b/libs/ui/widgets/kis_stopgradient_slider_widget.cpp @@ -1,368 +1,367 @@ /* * Copyright (c) 2004 Cyrille Berger * 2016 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 "widgets/kis_stopgradient_slider_widget.h" #include #include #include #include #include #include #include #include #include #include #include "kis_global.h" #include "kis_debug.h" #include "krita_utils.h" KisStopGradientSliderWidget::KisStopGradientSliderWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f) , m_selectedStop(0) , m_drag(0) { QLinearGradient defaultGradient; m_defaultGradient.reset(KoStopGradient::fromQGradient(&defaultGradient)); setGradientResource(0); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); setMouseTracking(true); QWindow *window = this->window()->windowHandle(); if (window) { connect(window, SIGNAL(screenChanged(QScreen*)), SLOT(updateHandleSize())); } updateHandleSize(); } void KisStopGradientSliderWidget::updateHandleSize() { QFontMetrics fm(font()); const int h = fm.height(); m_handleSize = QSize(0.34 * h, h); } int KisStopGradientSliderWidget::handleClickTolerance() const { // the size of the default text! return m_handleSize.width(); } void KisStopGradientSliderWidget::setGradientResource(KoStopGradient* gradient) { m_gradient = gradient ? gradient : m_defaultGradient.data(); if (m_gradient && m_selectedStop >= 0) { m_selectedStop = qBound(0, m_selectedStop, m_gradient->stops().size() - 1); emit sigSelectedStop(m_selectedStop); } else { m_selectedStop = -1; } } void KisStopGradientSliderWidget::paintHandle(qreal position, const QColor &color, bool isSelected, QPainter *painter) { const QRect handlesRect = this->handlesStipeRect(); const int handleCenter = handlesRect.left() + position * handlesRect.width(); const int handlesHalfWidth = handlesRect.height() * 0.26; // = 1.0 / 0.66 * 0.34 / 2.0 <-- golden ratio QPolygon triangle(3); triangle[0] = QPoint(handleCenter, handlesRect.top()); triangle[1] = QPoint(handleCenter - handlesHalfWidth, handlesRect.bottom()); triangle[2] = QPoint(handleCenter + handlesHalfWidth, handlesRect.bottom()); const qreal lineWidth = 1.0; if (!isSelected) { painter->setPen(QPen(palette().text(), lineWidth)); painter->setBrush(QBrush(color)); painter->setRenderHint(QPainter::Antialiasing); painter->drawPolygon(triangle); } else { painter->setPen(QPen(palette().highlight(), 1.5 * lineWidth)); painter->setBrush(QBrush(color)); painter->setRenderHint(QPainter::Antialiasing); painter->drawPolygon(triangle); } } void KisStopGradientSliderWidget::paintEvent(QPaintEvent* pe) { QWidget::paintEvent(pe); QPainter painter(this); painter.setPen(Qt::black); const QRect previewRect = gradientStripeRect(); KritaUtils::renderExactRect(&painter, kisGrowRect(previewRect, 1)); painter.drawRect(previewRect); if (m_gradient) { QImage image = m_gradient->generatePreview(previewRect.width(), previewRect.height()); if (!image.isNull()) { painter.drawImage(previewRect.topLeft(), image); } QList handlePositions = m_gradient->stops(); for (int i = 0; i < handlePositions.count(); i++) { if (i == m_selectedStop) continue; paintHandle(handlePositions[i].first, handlePositions[i].second.toQColor(), false, &painter); } if (m_selectedStop >= 0) { paintHandle(handlePositions[m_selectedStop].first, handlePositions[m_selectedStop].second.toQColor(), true, &painter); } } } int findNearestHandle(qreal t, const qreal tolerance, const QList &stops) { int result = -1; qreal minDistance = tolerance; for (int i = 0; i < stops.size(); i++) { const KoGradientStop &stop = stops[i]; const qreal distance = qAbs(t - stop.first); if (distance < minDistance) { minDistance = distance; result = i; } } return result; } void KisStopGradientSliderWidget::mousePressEvent(QMouseEvent * e) { if (!allowedClickRegion(handleClickTolerance()).contains(e->pos())) { QWidget::mousePressEvent(e); return; } if (e->buttons() != Qt::LeftButton ) { QWidget::mousePressEvent(e); return; } const QRect handlesRect = this->handlesStipeRect(); const qreal t = (qreal(e->x()) - handlesRect.x()) / handlesRect.width(); const QList stops = m_gradient->stops(); const int clickedStop = findNearestHandle(t, qreal(handleClickTolerance()) / handlesRect.width(), stops); if (clickedStop >= 0) { if (m_selectedStop != clickedStop) { m_selectedStop = clickedStop; emit sigSelectedStop(m_selectedStop); } m_drag = true; } else { insertStop(qBound(0.0, t, 1.0)); m_drag = true; } update(); updateCursor(e->pos()); } void KisStopGradientSliderWidget::mouseReleaseEvent(QMouseEvent * e) { Q_UNUSED(e); m_drag = false; updateCursor(e->pos()); } int getNewInsertPosition(const KoGradientStop &stop, const QList &stops) { int result = 0; for (int i = 0; i < stops.size(); i++) { if (stop.first <= stops[i].first) break; result = i + 1; } return result; } void KisStopGradientSliderWidget::mouseMoveEvent(QMouseEvent * e) { updateCursor(e->pos()); if (m_drag) { const QRect handlesRect = this->handlesStipeRect(); double t = (qreal(e->x()) - handlesRect.x()) / handlesRect.width(); QList stops = m_gradient->stops(); if (t < -0.1 || t > 1.1) { if (stops.size() > 2 && m_selectedStop >= 0) { m_removedStop = stops[m_selectedStop]; stops.removeAt(m_selectedStop); m_selectedStop = -1; } } else { if (m_selectedStop < 0) { m_removedStop.first = qBound(0.0, t, 1.0); const int newPos = getNewInsertPosition(m_removedStop, stops); stops.insert(newPos, m_removedStop); m_selectedStop = newPos; } else { KoGradientStop draggedStop = stops[m_selectedStop]; draggedStop.first = qBound(0.0, t, 1.0); stops.removeAt(m_selectedStop); const int newPos = getNewInsertPosition(draggedStop, stops); stops.insert(newPos, draggedStop); m_selectedStop = newPos; } } m_gradient->setStops(stops); emit sigSelectedStop(m_selectedStop); update(); } else { QWidget::mouseMoveEvent(e); } } void KisStopGradientSliderWidget::updateCursor(const QPoint &pos) { const bool isInAllowedRegion = allowedClickRegion(handleClickTolerance()).contains(pos); QCursor currentCursor; if (isInAllowedRegion) { const QRect handlesRect = this->handlesStipeRect(); const qreal t = (qreal(pos.x()) - handlesRect.x()) / handlesRect.width(); const QList stops = m_gradient->stops(); const int clickedStop = findNearestHandle(t, qreal(handleClickTolerance()) / handlesRect.width(), stops); if (clickedStop >= 0) { currentCursor = m_drag ? Qt::ClosedHandCursor : Qt::OpenHandCursor; } } if (currentCursor.shape() != Qt::ArrowCursor) { setCursor(currentCursor); } else { unsetCursor(); } } void KisStopGradientSliderWidget::insertStop(double t) { KIS_ASSERT_RECOVER(t >= 0 && t <= 1.0 ) { t = qBound(0.0, t, 1.0); } QList stops = m_gradient->stops(); KoColor color; m_gradient->colorAt(color, t); const KoGradientStop stop(t, color); const int newPos = getNewInsertPosition(stop, stops); stops.insert(newPos, stop); m_gradient->setStops(stops); m_selectedStop = newPos; emit sigSelectedStop(m_selectedStop); } QRect KisStopGradientSliderWidget::sliderRect() const { return QRect(QPoint(), size()).adjusted(m_handleSize.width(), 1, -m_handleSize.width(), -1); } QRect KisStopGradientSliderWidget::gradientStripeRect() const { const QRect rc = sliderRect(); return rc.adjusted(0, 0, 0, -m_handleSize.height()); } QRect KisStopGradientSliderWidget::handlesStipeRect() const { const QRect rc = sliderRect(); return rc.adjusted(0, rc.height() - m_handleSize.height(), 0, 0); } QRegion KisStopGradientSliderWidget::allowedClickRegion(int tolerance) const { QRegion result; result += sliderRect(); result += handlesStipeRect().adjusted(-tolerance, 0, tolerance, 0); return result; } int KisStopGradientSliderWidget::selectedStop() { return m_selectedStop; } void KisStopGradientSliderWidget::setSelectedStop(int selected) { m_selectedStop = selected; emit sigSelectedStop(m_selectedStop); update(); } int KisStopGradientSliderWidget::minimalHeight() const { QFontMetrics fm(font()); const int h = fm.height(); QStyleOptionToolButton opt; - QSize sz = (style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(h, h), this). - expandedTo(QApplication::globalStrut())); + QSize sz = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(h, h), this); return sz.height() + m_handleSize.height(); } QSize KisStopGradientSliderWidget::sizeHint() const { const int h = minimalHeight(); return QSize(2 * h, h); } QSize KisStopGradientSliderWidget::minimumSizeHint() const { const int h = minimalHeight(); return QSize(h, h); } diff --git a/libs/version/kritaversion.h.cmake b/libs/version/kritaversion.h.cmake index d9f56ab093..8cbd89ad03 100644 --- a/libs/version/kritaversion.h.cmake +++ b/libs/version/kritaversion.h.cmake @@ -1,172 +1,162 @@ /* This file is part of the Krita libraries Copyright (c) 2003 David Faure Copyright (c) 2003 Lukas Tinkl Copyright (c) 2004 Nicolas Goutte Copyright (C) 2015 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License 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 _KRITA_VERSION_H_ #define _KRITA_VERSION_H_ #include "kritaversion_export.h" // -- WARNING: do not edit values below, instead edit KRITA_* in /CMakeLists.txt -- /** * @def KRITA_VERSION_STRING * @ingroup KritaMacros * @brief Version of Krita as string, at compile time * * This macro contains the Krita version in string form. As it is a macro, * it contains the version at compile time. * * @note The version string might contain spaces and special characters, * especially for development versions of Krita. * If you use that macro directly for a file format (e.g. OASIS Open Document) * or for a protocol (e.g. http) be careful that it is appropriate. * (Fictional) example: "3.0 Alpha" */ #define KRITA_VERSION_STRING "@KRITA_VERSION_STRING@" /** * @def KRITA_STABLE_VERSION_MAJOR * @ingroup KritaMacros * @brief Major version of stable Krita, at compile time * KRITA_VERSION_MAJOR is computed based on this value. */ #define KRITA_STABLE_VERSION_MAJOR @KRITA_STABLE_VERSION_MAJOR@ /** * @def KRITA_VERSION_MAJOR * @ingroup KritaMacros * @brief Major version of Krita, at compile time * * Generally it's the same as KRITA_STABLE_VERSION_MAJOR but for unstable x.0 * x is decreased by one, e.g. 3.0 Beta is 2.99. */ #if !defined KRITA_STABLE && @KRITA_STABLE_VERSION_MINOR@ == 0 # define KRITA_VERSION_MAJOR (KRITA_STABLE_VERSION_MAJOR - 1) #else # define KRITA_VERSION_MAJOR KRITA_STABLE_VERSION_MAJOR #endif /** * @def KRITA_STABLE_VERSION_MINOR * @ingroup KritaMacros * @brief Minor version of stable Krita, at compile time * KRITA_VERSION_MINOR is computed based on this value. */ #define KRITA_STABLE_VERSION_MINOR @KRITA_STABLE_VERSION_MINOR@ /** * @def KRITA_VERSION_MINOR * @ingroup KritaMacros * @brief Minor version of Krita, at compile time * * Generally it's equal to KRITA_STABLE_VERSION_MINOR for stable releases, * equal to 99 for x.0 unstable releases (e.g. it's 3.0 Beta has minor version 99), * and equal to KRITA_STABLE_VERSION_MINOR-1 for unstable releases other than x.0. */ #ifdef KRITA_STABLE # define KRITA_VERSION_MINOR KRITA_STABLE_VERSION_MINOR #elif KRITA_STABLE_VERSION_MINOR == 0 # define KRITA_VERSION_MINOR 99 #else # define KRITA_VERSION_MINOR (KRITA_STABLE_VERSION_MINOR - 1) #endif /** * @def KRITA_VERSION_RELEASE * @ingroup KritaMacros * @brief Release version of Krita, at compile time. * 89 for Alpha. */ #define KRITA_VERSION_RELEASE @KRITA_VERSION_RELEASE@ /** * @def KRITA_STABLE_VERSION_RELEASE * @ingroup KritaMacros * @brief Release version of Krita, at compile time. * * Equal to KRITA_VERSION_RELEASE for stable releases and 0 for unstable ones. */ #ifdef KRITA_STABLE # define KRITA_STABLE_VERSION_RELEASE 0 #else # define KRITA_STABLE_VERSION_RELEASE @KRITA_VERSION_RELEASE@ #endif /** * @def KRITA_ALPHA * @ingroup KritaMacros * @brief If defined (1..9), indicates at compile time that Krita is in alpha stage */ #cmakedefine KRITA_ALPHA @KRITA_ALPHA@ /** * @def KRITA_BETA * @ingroup KritaMacros * @brief If defined (1..9), indicates at compile time that Krita is in beta stage */ #cmakedefine KRITA_BETA @KRITA_BETA@ /** * @def KRITA_RC * @ingroup KritaMacros * @brief If defined (1..9), indicates at compile time that Krita is in "release candidate" stage */ #cmakedefine KRITA_RC @KRITA_RC@ /** * @def KRITA_STABLE * @ingroup KritaMacros * @brief If defined, indicates at compile time that Krita is in stable stage */ #cmakedefine KRITA_STABLE @KRITA_STABLE@ /** * @ingroup KritaMacros * @brief Make a number from the major, minor and release number of a Krita version * * This function can be used for preprocessing when KRITA_IS_VERSION is not * appropriate. */ #define KRITA_MAKE_VERSION( a,b,c ) (((a) << 16) | ((b) << 8) | (c)) /** * @ingroup KritaMacros * @brief Version of Krita as number, at compile time * * This macro contains the Krita version in number form. As it is a macro, * it contains the version at compile time. See version() if you need * the Krita version used at runtime. */ #define KRITA_VERSION \ KRITA_MAKE_VERSION(KRITA_VERSION_MAJOR,KRITA_VERSION_MINOR,KRITA_VERSION_RELEASE) -/** - * @def KRITA_YEAR - * @ingroup KritaMacros - * @brief Year of the Krita release, set at compile time - * - * This macro is used in "About application" dialog for strings such as "© 2012-..., The Author Team". -*/ -#define KRITA_YEAR "@KRITA_YEAR@" - - #endif // _KRITA_VERSION_H_ diff --git a/libs/widgets/KisDlgInternalColorSelector.cpp b/libs/widgets/KisDlgInternalColorSelector.cpp index f228e43b6f..dfce1dac69 100644 --- a/libs/widgets/KisDlgInternalColorSelector.cpp +++ b/libs/widgets/KisDlgInternalColorSelector.cpp @@ -1,354 +1,348 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include #include #include #include #include #include #include "kis_signal_compressor.h" #include "KoColorDisplayRendererInterface.h" #include "kis_spinbox_color_selector.h" #include "KisDlgInternalColorSelector.h" #include "ui_WdgDlgInternalColorSelector.h" #include "kis_config_notifier.h" #include "kis_color_input.h" #include "kis_icon_utils.h" #include "KisSqueezedComboBox.h" std::function KisDlgInternalColorSelector::s_screenColorPickerFactory = 0; struct KisDlgInternalColorSelector::Private { bool allowUpdates = true; KoColor currentColor; KoColor previousColor; KoColor sRGB = KoColor(KoColorSpaceRegistry::instance()->rgb8()); const KoColorSpace *currentColorSpace; bool lockUsedCS = false; bool chooseAlpha = false; KisSignalCompressor *compressColorChanges; const KoColorDisplayRendererInterface *displayRenderer; KisHexColorInput *hexColorInput = 0; KisPaletteModel *paletteModel = 0; KisPaletteListWidget *paletteChooser = 0; KisScreenColorPickerBase *screenColorPicker = 0; }; KisDlgInternalColorSelector::KisDlgInternalColorSelector(QWidget *parent, KoColor color, Config config, const QString &caption, const KoColorDisplayRendererInterface *displayRenderer) : QDialog(parent) , m_d(new Private) { setModal(config.modal); setFocusPolicy(Qt::ClickFocus); m_ui = new Ui_WdgDlgInternalColorSelector(); m_ui->setupUi(this); setWindowTitle(caption); m_d->currentColor = color; m_d->currentColorSpace = m_d->currentColor.colorSpace(); m_d->displayRenderer = displayRenderer; m_ui->spinboxselector->slotSetColor(color); connect(m_ui->spinboxselector, SIGNAL(sigNewColor(KoColor)), this, SLOT(slotColorUpdated(KoColor))); m_ui->visualSelector->slotSetColor(color); m_ui->visualSelector->setDisplayRenderer(displayRenderer); m_ui->visualSelector->setConfig(false, config.modal); if (config.visualColorSelector) { connect(m_ui->visualSelector, SIGNAL(sigNewColor(KoColor)), this, SLOT(slotColorUpdated(KoColor))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), m_ui->visualSelector, SLOT(configurationChanged())); } else { m_ui->visualSelector->hide(); } m_d->paletteChooser = new KisPaletteListWidget(this); m_d->paletteModel = new KisPaletteModel(this); m_ui->bnPaletteChooser->setIcon(KisIconUtils::loadIcon("hi16-palette_library")); m_ui->paletteBox->setPaletteModel(m_d->paletteModel); m_ui->paletteBox->setDisplayRenderer(displayRenderer); m_ui->cmbNameList->setCompanionView(m_ui->paletteBox); connect(m_d->paletteChooser, SIGNAL(sigPaletteSelected(KoColorSet*)), this, SLOT(slotChangePalette(KoColorSet*))); connect(m_ui->cmbNameList, SIGNAL(sigColorSelected(KoColor)), SLOT(slotColorUpdated(KoColor))); // For some bizarre reason, the modal dialog doesn't like having the colorset set, so let's not. if (config.paletteBox) { //TODO: Add disable signal as well. Might be not necessary...? KConfigGroup cfg(KSharedConfig::openConfig()->group("")); QString paletteName = cfg.readEntry("internal_selector_active_color_set", QString()); KoResourceServer* rServer = KoResourceServerProvider::instance()->paletteServer(); KoColorSet *savedPal = rServer->resourceByName(paletteName); if (savedPal) { this->slotChangePalette(savedPal); } else { if (rServer->resources().count()) { savedPal = rServer->resources().first(); if (savedPal) { this->slotChangePalette(savedPal); } } } connect(m_ui->paletteBox, SIGNAL(sigColorSelected(KoColor)), this, SLOT(slotColorUpdated(KoColor))); m_ui->bnPaletteChooser->setPopupWidget(m_d->paletteChooser); } else { m_ui->paletteBox->setEnabled(false); m_ui->cmbNameList->setEnabled(false); m_ui->bnPaletteChooser->setEnabled(false); } if (config.prevNextButtons) { m_ui->currentColor->setColor(m_d->currentColor); m_ui->currentColor->setDisplayRenderer(displayRenderer); m_ui->previousColor->setColor(m_d->currentColor); m_ui->previousColor->setDisplayRenderer(displayRenderer); connect(m_ui->previousColor, SIGNAL(triggered(KoColorPatch*)), SLOT(slotSetColorFromPatch(KoColorPatch*))); } else { m_ui->currentColor->hide(); m_ui->previousColor->hide(); } if (config.hexInput) { m_d->sRGB.fromKoColor(m_d->currentColor); m_d->hexColorInput = new KisHexColorInput(this, &m_d->sRGB); m_d->hexColorInput->update(); connect(m_d->hexColorInput, SIGNAL(updated()), SLOT(slotSetColorFromHex())); m_ui->rightPane->addWidget(m_d->hexColorInput); m_d->hexColorInput->setToolTip(i18n("This is a hexcode input, for webcolors. It can only get colors in the sRGB space.")); } // KisScreenColorPicker is in the kritaui module, so dependency inversion is used to access it. m_ui->screenColorPickerWidget->setLayout(new QHBoxLayout(m_ui->screenColorPickerWidget)); if (s_screenColorPickerFactory) { m_d->screenColorPicker = s_screenColorPickerFactory(m_ui->screenColorPickerWidget); m_ui->screenColorPickerWidget->layout()->addWidget(m_d->screenColorPicker); if (config.screenColorPicker) { connect(m_d->screenColorPicker, SIGNAL(sigNewColorPicked(KoColor)),this, SLOT(slotColorUpdated(KoColor))); } else { m_d->screenColorPicker->hide(); } } m_d->compressColorChanges = new KisSignalCompressor(100 /* ms */, KisSignalCompressor::POSTPONE, this); connect(m_d->compressColorChanges, SIGNAL(timeout()), this, SLOT(endUpdateWithNewColor())); connect(m_ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()), Qt::UniqueConnection); connect(m_ui->buttonBox, SIGNAL(rejected()), this, SLOT(reject()), Qt::UniqueConnection); connect(this, SIGNAL(finished(int)), SLOT(slotFinishUp())); } KisDlgInternalColorSelector::~KisDlgInternalColorSelector() { delete m_ui; } void KisDlgInternalColorSelector::slotColorUpdated(KoColor newColor) { // not-so-nice solution: if someone calls this slot directly and that code was // triggered by our compressor signal, our compressor is technically the sender()! if (sender() == m_d->compressColorChanges) { return; } // Do not accept external updates while a color update emit is pending; // Note: Assumes external updates only come from parent(), a separate slot might be better if (m_d->allowUpdates || (QObject::sender() && QObject::sender() != this->parent())) { // Enforce palette colors KConfigGroup group(KSharedConfig::openConfig(), ""); if (group.readEntry("colorsettings/forcepalettecolors", false)) { newColor = m_ui->paletteBox->closestColor(newColor); } if (m_d->lockUsedCS){ newColor.convertTo(m_d->currentColorSpace); m_d->currentColor = newColor; } else { m_d->currentColor = newColor; } updateAllElements(QObject::sender()); } } void KisDlgInternalColorSelector::slotSetColorFromPatch(KoColorPatch *patch) { slotColorUpdated(patch->color()); } void KisDlgInternalColorSelector::colorSpaceChanged(const KoColorSpace *cs) { if (cs == m_d->currentColorSpace) { return; } m_d->currentColorSpace = KoColorSpaceRegistry::instance()->colorSpace(cs->colorModelId().id(), cs->colorDepthId().id(), cs->profile()); m_ui->spinboxselector->slotSetColorSpace(m_d->currentColorSpace); m_ui->visualSelector->slotsetColorSpace(m_d->currentColorSpace); } void KisDlgInternalColorSelector::lockUsedColorSpace(const KoColorSpace *cs) { colorSpaceChanged(cs); if (m_d->currentColor.colorSpace() != m_d->currentColorSpace) { m_d->currentColor.convertTo(m_d->currentColorSpace); } m_d->lockUsedCS = true; } void KisDlgInternalColorSelector::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer) { if (displayRenderer) { m_d->displayRenderer = displayRenderer; m_ui->visualSelector->setDisplayRenderer(displayRenderer); m_ui->currentColor->setDisplayRenderer(displayRenderer); m_ui->previousColor->setDisplayRenderer(displayRenderer); m_ui->paletteBox->setDisplayRenderer(displayRenderer); } else { m_d->displayRenderer = KoDumbColorDisplayRenderer::instance(); } } KoColor KisDlgInternalColorSelector::getModalColorDialog(const KoColor color, QWidget* parent, QString caption) { Config config = Config(); KisDlgInternalColorSelector dialog(parent, color, config, caption); dialog.setPreviousColor(color); dialog.exec(); return dialog.getCurrentColor(); } KoColor KisDlgInternalColorSelector::getCurrentColor() { return m_d->currentColor; } void KisDlgInternalColorSelector::chooseAlpha(bool chooseAlpha) { m_d->chooseAlpha = chooseAlpha; } -void KisDlgInternalColorSelector::slotConfigurationChanged() -{ - //m_d->canvas->displayColorConverter()-> - //slotColorSpaceChanged(m_d->canvas->image()->colorSpace()); -} - void KisDlgInternalColorSelector::setPreviousColor(KoColor c) { m_d->previousColor = c; } void KisDlgInternalColorSelector::reject() { slotColorUpdated(m_d->previousColor); QDialog::reject(); } void KisDlgInternalColorSelector::updateAllElements(QObject *source) { //update everything!!! if (source != m_ui->spinboxselector) { m_ui->spinboxselector->slotSetColor(m_d->currentColor); } if (source != m_ui->visualSelector) { m_ui->visualSelector->slotSetColor(m_d->currentColor); } if (source != m_d->hexColorInput) { m_d->sRGB.fromKoColor(m_d->currentColor); m_d->hexColorInput->update(); } if (source != m_ui->paletteBox) { m_ui->paletteBox->selectClosestColor(m_d->currentColor); } m_ui->previousColor->setColor(m_d->previousColor); m_ui->currentColor->setColor(m_d->currentColor); if (source && source != this->parent()) { m_d->allowUpdates = false; m_d->compressColorChanges->start(); } if (m_d->screenColorPicker) { m_d->screenColorPicker->updateIcons(); } } void KisDlgInternalColorSelector::endUpdateWithNewColor() { emit signalForegroundColorChosen(m_d->currentColor); m_d->allowUpdates = true; } void KisDlgInternalColorSelector::focusInEvent(QFocusEvent *) { //setPreviousColor(); } void KisDlgInternalColorSelector::slotFinishUp() { setPreviousColor(m_d->currentColor); KConfigGroup cfg(KSharedConfig::openConfig()->group("")); if (m_d->paletteModel) { if (m_d->paletteModel->colorSet()) { cfg.writeEntry("internal_selector_active_color_set", m_d->paletteModel->colorSet()->name()); } } } void KisDlgInternalColorSelector::slotSetColorFromHex() { slotColorUpdated(m_d->sRGB); } void KisDlgInternalColorSelector::slotChangePalette(KoColorSet *set) { if (!set) { return; } m_d->paletteModel->setPalette(set); } void KisDlgInternalColorSelector::showEvent(QShowEvent *event) { updateAllElements(0); QDialog::showEvent(event); } diff --git a/libs/widgets/KisDlgInternalColorSelector.h b/libs/widgets/KisDlgInternalColorSelector.h index 0536d0a42f..424e2c8042 100644 --- a/libs/widgets/KisDlgInternalColorSelector.h +++ b/libs/widgets/KisDlgInternalColorSelector.h @@ -1,192 +1,187 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISDLGINTERNALCOLORSELECTOR_H #define KISDLGINTERNALCOLORSELECTOR_H #include "kritawidgets_export.h" #include "KoColor.h" #include "KoColorSpace.h" #include "KoColorDisplayRendererInterface.h" #include "KoColorSet.h" #include #include #include "KisScreenColorPickerBase.h" class Ui_WdgDlgInternalColorSelector; class KoColorPatch; /** * @brief The KisInternalColorSelector class * * A non-modal color selector dialog that is not a plugin and can thus be used for filters. */ class KRITAWIDGETS_EXPORT KisDlgInternalColorSelector : public QDialog { Q_OBJECT static std::function s_screenColorPickerFactory; public: static void setScreenColorPickerFactory(std::function f) { s_screenColorPickerFactory = f; } struct Config { Config() : modal(true), visualColorSelector(true), paletteBox(true), screenColorPicker(true), prevNextButtons(true), hexInput(true), useAlpha(false){} bool modal; bool visualColorSelector; bool paletteBox; bool screenColorPicker; bool prevNextButtons; bool hexInput; bool useAlpha; }; KisDlgInternalColorSelector(QWidget* parent, KoColor color, Config config, const QString &caption, const KoColorDisplayRendererInterface *displayRenderer = KoDumbColorDisplayRenderer::instance()); ~KisDlgInternalColorSelector() override; /** * @brief slotColorSpaceChanged * Color space has changed, use this dialog to change the colorspace. */ void colorSpaceChanged(const KoColorSpace *cs); /** * @brief lockUsedColorSpace * Lock the used colorspace of this selector. * @param cs */ void lockUsedColorSpace(const KoColorSpace *cs); /** * @brief setDisplayRenderer * Set the display renderer. This is necessary for HDR color manage support. * @param displayRenderer */ void setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer); /** * @brief getModalColorDialog * Execute this dialog modally. The function returns * the KoColor you want. * @param color - The current color. Make sure this is in the color space you want your * end color to be in. * @param parent parent widget. * @param caption the dialog caption. */ static KoColor getModalColorDialog(const KoColor color, QWidget* parent = 0, QString caption = QString()); /** * @brief getCurrentColor * @return gives currently active color; */ KoColor getCurrentColor(); void chooseAlpha(bool chooseAlpha); Q_SIGNALS: /** * @brief signalForegroundColorChosen * The most important signal. This will sent out when a color has been picked from the selector. * There will be a small delay to make sure that the selector causes too many updates. * * Do not connect this to slotColorUpdated. * @param color The new color chosen */ void signalForegroundColorChosen(KoColor color); public Q_SLOTS: /** * @brief slotColorUpdated * Very important slot. Is connected to krita's resources to make sure it has * the currently active color. It's very important that this function is able to understand * when the signal came from itself. * @param newColor This is the new color. */ void slotColorUpdated(KoColor newColor); /** * @brief slotSetColorFromPatch * update current color from kocolorpatch. * @param patch */ void slotSetColorFromPatch(KoColorPatch* patch); /** * @brief setPreviousColor * set the previous color. */ void setPreviousColor(KoColor c); void reject() override; private Q_SLOTS: - /** - * @brief slotConfigurationChanged - * Wrapper slot for changes to the colorspace. - */ - void slotConfigurationChanged(); void endUpdateWithNewColor(); /** * @brief slotFinishUp * This is called when the selector is closed, for saving the current palette. */ void slotFinishUp(); /** * @brief slotSetColorFromHex * Update from the hex color input. */ void slotSetColorFromHex(); void slotChangePalette(KoColorSet *set); protected: void showEvent(QShowEvent *event) override; private: void focusInEvent(QFocusEvent *) override; /** * @brief updateAllElements * Updates each widget with the new element, and if it's responsible for the update sents * a signal out that there's a new color. */ void updateAllElements(QObject *source); private: Ui_WdgDlgInternalColorSelector *m_ui; struct Private; //The private struct const QScopedPointer m_d; //the private pointer }; #endif // KISDLGINTERNALCOLORSELECTOR_H diff --git a/libs/widgets/KisPaletteModel.cpp b/libs/widgets/KisPaletteModel.cpp index f1c4e71f8b..f7a4526d01 100644 --- a/libs/widgets/KisPaletteModel.cpp +++ b/libs/widgets/KisPaletteModel.cpp @@ -1,508 +1,511 @@ /* * Copyright (c) 2013 Sven Langkamp * Copyright (c) 2018 Michael Zhou * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisPaletteModel.h" #include #include #include #include #include #include #include #include #include KisPaletteModel::KisPaletteModel(QObject* parent) : QAbstractTableModel(parent) , m_colorSet(0) , m_displayRenderer(KoDumbColorDisplayRenderer::instance()) { connect(this, SIGNAL(sigPaletteModified()), SLOT(slotPaletteModified())); } KisPaletteModel::~KisPaletteModel() { } QVariant KisPaletteModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } bool groupNameRow = m_rowGroupNameMap.contains(index.row()); if (role == IsGroupNameRole) { return groupNameRow; } if (groupNameRow) { return dataForGroupNameRow(index, role); } else { return dataForSwatch(index, role); } } int KisPaletteModel::rowCount(const QModelIndex& /*parent*/) const { if (!m_colorSet) return 0; return m_colorSet->rowCount() // count of color rows + m_rowGroupNameMap.size() // rows for names - 1; // global doesn't have a name } int KisPaletteModel::columnCount(const QModelIndex& /*parent*/) const { if (m_colorSet && m_colorSet->columnCount() > 0) { return m_colorSet->columnCount(); } if (!m_colorSet) { return 0; } return 16; } Qt::ItemFlags KisPaletteModel::flags(const QModelIndex& index) const { if (index.isValid()) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; } return Qt::ItemIsDropEnabled; } QModelIndex KisPaletteModel::index(int row, int column, const QModelIndex& parent) const { - Q_UNUSED(parent); + Q_UNUSED(parent) Q_ASSERT(m_colorSet); + if (m_rowGroupNameMap.isEmpty()) { + return {}; + } int groupNameRow = groupNameRowForRow(row); KisSwatchGroup *group = m_colorSet->getGroup(m_rowGroupNameMap[groupNameRow]); - Q_ASSERT(group); + KIS_ASSERT_RECOVER_RETURN_VALUE(group,QModelIndex()); return createIndex(row, column, group); } void KisPaletteModel::resetGroupNameRows() { m_rowGroupNameMap.clear(); int row = -1; for (const QString &groupName : m_colorSet->getGroupNames()) { m_rowGroupNameMap[row] = groupName; row += m_colorSet->getGroup(groupName)->rowCount(); row += 1; // row for group name } } void KisPaletteModel::setPalette(KoColorSet* palette) { beginResetModel(); m_colorSet = palette; if (palette) { resetGroupNameRows(); } endResetModel(); emit sigPaletteChanged(); } KoColorSet* KisPaletteModel::colorSet() const { return m_colorSet; } int KisPaletteModel::rowNumberInGroup(int rowInModel) const { if (m_rowGroupNameMap.contains(rowInModel)) { return -1; } QList rowNumberList = m_rowGroupNameMap.keys(); for (auto it = rowNumberList.rbegin(); it != rowNumberList.rend(); it++) { if (*it < rowInModel) { return rowInModel - *it - 1; } } return rowInModel; } int KisPaletteModel::groupNameRowForName(const QString &groupName) { for (auto it = m_rowGroupNameMap.begin(); it != m_rowGroupNameMap.end(); it++) { if (it.value() == groupName) { return it.key(); } } return -1; } bool KisPaletteModel::addEntry(const KisSwatch &entry, const QString &groupName) { beginInsertRows(QModelIndex(), rowCount(), rowCount() + 1); m_colorSet->add(entry, groupName); endInsertRows(); if (m_colorSet->isGlobal()) { m_colorSet->save(); } emit sigPaletteModified(); return true; } bool KisPaletteModel::removeEntry(const QModelIndex &index, bool keepColors) { if (!qvariant_cast(data(index, IsGroupNameRole))) { static_cast(index.internalPointer())->removeEntry(index.column(), rowNumberInGroup(index.row())); emit dataChanged(index, index); } else { int groupNameRow = groupNameRowForRow(index.row()); QString groupName = m_rowGroupNameMap[groupNameRow]; removeGroup(groupName, keepColors); } emit sigPaletteModified(); return true; } void KisPaletteModel::removeGroup(const QString &groupName, bool keepColors) { int removeStart = groupNameRowForName(groupName); int removedRowCount = m_colorSet->getGroup(groupName)->rowCount(); int insertStart = m_colorSet->getGlobalGroup()->rowCount(); beginRemoveRows(QModelIndex(), removeStart, removeStart + removedRowCount); m_colorSet->removeGroup(groupName, keepColors); resetGroupNameRows(); endRemoveRows(); beginInsertRows(QModelIndex(), insertStart, m_colorSet->getGlobalGroup()->rowCount()); endInsertRows(); emit sigPaletteModified(); } bool KisPaletteModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row); Q_UNUSED(column); if (!data->hasFormat("krita/x-colorsetentry") && !data->hasFormat("krita/x-colorsetgroup")) { return false; } if (action == Qt::IgnoreAction) { return false; } QModelIndex finalIndex = parent; if (!finalIndex.isValid()) { return false; } if (data->hasFormat("krita/x-colorsetgroup")) { // dragging group not supported for now QByteArray encodedData = data->data("krita/x-colorsetgroup"); QDataStream stream(&encodedData, QIODevice::ReadOnly); while (!stream.atEnd()) { QString groupNameDroppedOn = qvariant_cast(finalIndex.data(GroupNameRole)); if (groupNameDroppedOn == KoColorSet::GLOBAL_GROUP_NAME) { return false; } QString groupNameDragged; stream >> groupNameDragged; KisSwatchGroup *groupDragged = m_colorSet->getGroup(groupNameDragged); int start = groupNameRowForName(groupNameDragged); int end = start + groupDragged->rowCount(); if (!beginMoveRows(QModelIndex(), start, end, QModelIndex(), groupNameRowForName(groupNameDroppedOn))) { return false; } m_colorSet->moveGroup(groupNameDragged, groupNameDroppedOn); resetGroupNameRows(); endMoveRows(); emit sigPaletteModified(); if (m_colorSet->isGlobal()) { m_colorSet->save(); } } return true; } if (qvariant_cast(finalIndex.data(KisPaletteModel::IsGroupNameRole))) { return true; } QByteArray encodedData = data->data("krita/x-colorsetentry"); QDataStream stream(&encodedData, QIODevice::ReadOnly); while (!stream.atEnd()) { KisSwatch entry; QString name, id; bool spotColor; QString oldGroupName; int oriRow; int oriColumn; QString colorXml; stream >> name >> id >> spotColor >> oriRow >> oriColumn >> oldGroupName >> colorXml; entry.setName(name); entry.setId(id); entry.setSpotColor(spotColor); QDomDocument doc; doc.setContent(colorXml); QDomElement e = doc.documentElement(); QDomElement c = e.firstChildElement(); if (!c.isNull()) { QString colorDepthId = c.attribute("bitdepth", Integer8BitsColorDepthID.id()); entry.setColor(KoColor::fromXML(c, colorDepthId)); } if (action == Qt::MoveAction){ KisSwatchGroup *g = m_colorSet->getGroup(oldGroupName); if (g) { if (qvariant_cast(finalIndex.data(KisPaletteModel::CheckSlotRole))) { g->setEntry(getEntry(finalIndex), oriColumn, oriRow); } else { g->removeEntry(oriColumn, oriRow); } } setEntry(entry, finalIndex); emit sigPaletteModified(); if (m_colorSet->isGlobal()) { m_colorSet->save(); } } } return true; } QMimeData *KisPaletteModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); QModelIndex index = indexes.last(); if (index.isValid() && qvariant_cast(index.data(CheckSlotRole))) { QString mimeTypeName = "krita/x-colorsetentry"; if (qvariant_cast(index.data(IsGroupNameRole))==false) { KisSwatch entry = getEntry(index); QDomDocument doc; QDomElement root = doc.createElement("Color"); root.setAttribute("bitdepth", entry.color().colorSpace()->colorDepthId().id()); doc.appendChild(root); entry.color().toXML(doc, root); stream << entry.name() << entry.id() << entry.spotColor() << rowNumberInGroup(index.row()) << index.column() << qvariant_cast(index.data(GroupNameRole)) << doc.toString(); } else { mimeTypeName = "krita/x-colorsetgroup"; QString groupName = qvariant_cast(index.data(GroupNameRole)); stream << groupName; } mimeData->setData(mimeTypeName, encodedData); } return mimeData; } QStringList KisPaletteModel::mimeTypes() const { return QStringList() << "krita/x-colorsetentry" << "krita/x-colorsetgroup"; } Qt::DropActions KisPaletteModel::supportedDropActions() const { return Qt::MoveAction; } void KisPaletteModel::setEntry(const KisSwatch &entry, const QModelIndex &index) { KisSwatchGroup *group = static_cast(index.internalPointer()); Q_ASSERT(group); group->setEntry(entry, index.column(), rowNumberInGroup(index.row())); emit sigPaletteModified(); emit dataChanged(index, index); if (m_colorSet->isGlobal()) { m_colorSet->save(); } } bool KisPaletteModel::renameGroup(const QString &groupName, const QString &newName) { beginResetModel(); bool success = m_colorSet->changeGroupName(groupName, newName); for (auto it = m_rowGroupNameMap.begin(); it != m_rowGroupNameMap.end(); it++) { if (it.value() == groupName) { m_rowGroupNameMap[it.key()] = newName; break; } } endResetModel(); emit sigPaletteModified(); return success; } void KisPaletteModel::addGroup(const KisSwatchGroup &group) { beginInsertRows(QModelIndex(), rowCount(), rowCount() + group.rowCount()); m_colorSet->addGroup(group.name()); *m_colorSet->getGroup(group.name()) = group; endInsertColumns(); emit sigPaletteModified(); } void KisPaletteModel::setRowNumber(const QString &groupName, int rowCount) { beginResetModel(); KisSwatchGroup *g = m_colorSet->getGroup(groupName); if (g) { g->setRowCount(rowCount); } endResetModel(); } void KisPaletteModel::clear() { beginResetModel(); m_colorSet->clear(); endResetModel(); } QVariant KisPaletteModel::dataForGroupNameRow(const QModelIndex &idx, int role) const { KisSwatchGroup *group = static_cast(idx.internalPointer()); Q_ASSERT(group); QString groupName = group->name(); switch (role) { case Qt::ToolTipRole: case Qt::DisplayRole: { return groupName; } case GroupNameRole: { return groupName; } case CheckSlotRole: { return true; } case RowInGroupRole: { return -1; } default: { return QVariant(); } } } QVariant KisPaletteModel::dataForSwatch(const QModelIndex &idx, int role) const { KisSwatchGroup *group = static_cast(idx.internalPointer()); Q_ASSERT(group); int rowInGroup = rowNumberInGroup(idx.row()); bool entryPresent = group->checkEntry(idx.column(), rowInGroup); KisSwatch entry; if (entryPresent) { entry = group->getEntry(idx.column(), rowInGroup); } switch (role) { case Qt::ToolTipRole: case Qt::DisplayRole: { return entryPresent ? entry.name() : i18n("Empty slot"); } case Qt::BackgroundRole: { QColor color(0, 0, 0, 0); if (entryPresent) { color = m_displayRenderer->toQColor(entry.color()); } return QBrush(color); } case GroupNameRole: { return group->name(); } case CheckSlotRole: { return entryPresent; } case RowInGroupRole: { return rowInGroup; } default: { return QVariant(); } } } void KisPaletteModel::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer) { if (displayRenderer) { if (m_displayRenderer) { disconnect(m_displayRenderer, 0, this, 0); } m_displayRenderer = displayRenderer; connect(m_displayRenderer, SIGNAL(displayConfigurationChanged()), SLOT(slotDisplayConfigurationChanged()), Qt::UniqueConnection); } else { m_displayRenderer = KoDumbColorDisplayRenderer::instance(); } } void KisPaletteModel::slotDisplayConfigurationChanged() { beginResetModel(); endResetModel(); } void KisPaletteModel::slotPaletteModified() { m_colorSet->setPaletteType(KoColorSet::KPL); } QModelIndex KisPaletteModel::indexForClosest(const KoColor &compare) { KisSwatchGroup::SwatchInfo info = colorSet()->getClosestColorInfo(compare); return createIndex(indexRowForInfo(info), info.column, colorSet()->getGroup(info.group)); } int KisPaletteModel::indexRowForInfo(const KisSwatchGroup::SwatchInfo &info) { for (auto it = m_rowGroupNameMap.begin(); it != m_rowGroupNameMap.end(); it++) { if (it.value() == info.group) { return it.key() + info.row + 1; } } return info.row; } KisSwatch KisPaletteModel::getEntry(const QModelIndex &index) const { KisSwatchGroup *group = static_cast(index.internalPointer()); if (!group || !group->checkEntry(index.column(), rowNumberInGroup(index.row()))) { return KisSwatch(); } return group->getEntry(index.column(), rowNumberInGroup(index.row())); } int KisPaletteModel::groupNameRowForRow(int rowInModel) const { return rowInModel - rowNumberInGroup(rowInModel) - 1; } diff --git a/libs/widgets/KisVisualColorSelector.cpp b/libs/widgets/KisVisualColorSelector.cpp index d87947a2e1..e18eb62ee9 100644 --- a/libs/widgets/KisVisualColorSelector.cpp +++ b/libs/widgets/KisVisualColorSelector.cpp @@ -1,566 +1,563 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisVisualColorSelector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColorConversions.h" #include "KoColorDisplayRendererInterface.h" #include "KoColorProfile.h" #include "KoChannelInfo.h" #include #include #include "kis_signal_compressor.h" #include "kis_debug.h" #include "KisVisualColorSelectorShape.h" #include "KisVisualRectangleSelectorShape.h" #include "KisVisualTriangleSelectorShape.h" #include "KisVisualEllipticalSelectorShape.h" struct KisVisualColorSelector::Private { KoColor currentcolor; const KoColorSpace *currentCS {0}; QList widgetlist; bool updateLonesome {false}; // currently redundant; remove? bool circular {false}; bool exposureSupported = false; bool isRGBA = false; bool isLinear = false; int displayPosition[4]; // map channel index to storage index for display int colorChannelCount; QVector4D channelValues; QVector4D channelMaxValues; ColorModel model; const KoColorDisplayRendererInterface *displayRenderer {0}; KisColorSelectorConfiguration acs_config; KisSignalCompressor *updateTimer {0}; }; KisVisualColorSelector::KisVisualColorSelector(QWidget *parent) : KisColorSelectorInterface(parent) , m_d(new Private) { this->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); m_d->acs_config = KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString())); m_d->updateTimer = new KisSignalCompressor(100 /* ms */, KisSignalCompressor::POSTPONE); connect(m_d->updateTimer, SIGNAL(timeout()), SLOT(slotRebuildSelectors()), Qt::UniqueConnection); } KisVisualColorSelector::~KisVisualColorSelector() { delete m_d->updateTimer; } void KisVisualColorSelector::slotSetColor(const KoColor &c) { m_d->currentcolor = c; if (m_d->currentCS != c.colorSpace()) { slotsetColorSpace(c.colorSpace()); } else { m_d->channelValues = convertKoColorToShapeCoordinates(m_d->currentcolor); Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { shape->setChannelValues(m_d->channelValues, true); } } } void KisVisualColorSelector::slotsetColorSpace(const KoColorSpace *cs) { if (m_d->currentCS != cs) { m_d->currentCS = cs; slotRebuildSelectors(); } } void KisVisualColorSelector::setConfig(bool forceCircular, bool forceSelfUpdate) { m_d->circular = forceCircular; m_d->updateLonesome = forceSelfUpdate; } KoColor KisVisualColorSelector::getCurrentColor() const { return m_d->currentcolor; } QVector4D KisVisualColorSelector::getChannelValues() const { return m_d->channelValues; } KoColor KisVisualColorSelector::convertShapeCoordsToKoColor(const QVector4D &coordinates) const { KoColor c(m_d->currentCS); QVector4D baseValues(coordinates); QVector channelValues(c.colorSpace()->channelCount()); channelValues.fill(1.0); if (m_d->model != ColorModel::Channel && m_d->isRGBA == true) { if (m_d->model == ColorModel::HSV) { HSVToRGB(coordinates.x()*360, coordinates.y(), coordinates.z(), &baseValues[0], &baseValues[1], &baseValues[2]); } else if (m_d->model == ColorModel::HSL) { HSLToRGB(coordinates.x()*360, coordinates.y(), coordinates.z(), &baseValues[0], &baseValues[1], &baseValues[2]); } else if (m_d->model == ColorModel::HSI) { // why suddenly qreal? qreal temp[3]; HSIToRGB(coordinates.x(), coordinates.y(), coordinates.z(), &temp[0], &temp[1], &temp[2]); baseValues.setX(temp[0]); baseValues.setY(temp[1]); baseValues.setZ(temp[2]); } else /*if (m_d->model == ColorModel::HSY)*/ { QVector luma= m_d->currentCS->lumaCoefficients(); qreal temp[3]; HSYToRGB(coordinates.x(), coordinates.y(), coordinates.z(), &temp[0], &temp[1], &temp[2], luma[0], luma[1], luma[2]); baseValues.setX(temp[0]); baseValues.setY(temp[1]); baseValues.setZ(temp[2]); } if (m_d->isLinear) { for (int i=0; i<3; i++) { baseValues[i] = pow(baseValues[i], 2.2); } } } if (m_d->exposureSupported) { baseValues *= m_d->channelMaxValues; } for (int i=0; icolorChannelCount; i++) { // TODO: proper exposure control channelValues[m_d->displayPosition[i]] = baseValues[i] /* *(maxvalue[i]) */; } c.colorSpace()->fromNormalisedChannelsValue(c.data(), channelValues); return c; } QVector4D KisVisualColorSelector::convertKoColorToShapeCoordinates(KoColor c) const { if (c.colorSpace() != m_d->currentCS) { c.convertTo(m_d->currentCS); } QVector channelValues (c.colorSpace()->channelCount()); channelValues.fill(1.0); m_d->currentCS->normalisedChannelsValue(c.data(), channelValues); QVector4D channelValuesDisplay(0, 0, 0, 0), coordinates(0, 0, 0, 0); // TODO: L*a*b is apparently not [0, 1]^3 as "normalized" values, needs extra transform (old bug) for (int i =0; icolorChannelCount; i++) { channelValuesDisplay[i] = channelValues[m_d->displayPosition[i]]; } if (m_d->exposureSupported) { channelValuesDisplay /= m_d->channelMaxValues; } if (m_d->model != ColorModel::Channel && m_d->isRGBA == true) { if (m_d->isRGBA == true) { if (m_d->isLinear) { for (int i=0; i<3; i++) { channelValuesDisplay[i] = pow(channelValuesDisplay[i], 1/2.2); } } if (m_d->model == ColorModel::HSV){ QVector3D hsv; // TODO: handle undefined hue case (returns -1) RGBToHSV(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsv[0], &hsv[1], &hsv[2]); hsv[0] /= 360; coordinates = QVector4D(hsv, 0.f); } else if (m_d->model == ColorModel::HSL) { QVector3D hsl; RGBToHSL(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsl[0], &hsl[1], &hsl[2]); hsl[0] /= 360; coordinates = QVector4D(hsl, 0.f); } else if (m_d->model == ColorModel::HSI) { qreal hsi[3]; RGBToHSI(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsi[0], &hsi[1], &hsi[2]); coordinates = QVector4D(hsi[0], hsi[1], hsi[2], 0.f); } else if (m_d->model == ColorModel::HSY) { QVector luma = m_d->currentCS->lumaCoefficients(); qreal hsy[3]; RGBToHSY(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsy[0], &hsy[1], &hsy[2], luma[0], luma[1], luma[2]); coordinates = QVector4D(hsy[0], hsy[1], hsy[2], 0.f); } for (int i=0; i<3; i++) { coordinates[i] = qBound(0.f, coordinates[i], 1.f); } } } else { for (int i=0; i<4; i++) { coordinates[i] = qBound(0.f, channelValuesDisplay[i], 1.f); } } return coordinates; } void KisVisualColorSelector::configurationChanged() { if (m_d->updateTimer) { m_d->updateTimer->start(); } } void KisVisualColorSelector::slotDisplayConfigurationChanged() { Q_ASSERT(m_d->displayRenderer); if (m_d->currentCS) { m_d->channelMaxValues = QVector4D(1, 1, 1, 1); QList channels = m_d->currentCS->channels(); for (int i=0; icolorChannelCount; ++i) { m_d->channelMaxValues[i] = m_d->displayRenderer->maxVisibleFloatValue(channels[m_d->displayPosition[i]]); } // need to re-scale our normalized channel values on exposure changes: m_d->channelValues = convertKoColorToShapeCoordinates(m_d->currentcolor); Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { shape->setChannelValues(m_d->channelValues, true); } } } void KisVisualColorSelector::slotRebuildSelectors() { KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); m_d->acs_config = KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString())); QList channelList = m_d->currentCS->channels(); int cCount = 0; Q_FOREACH(const KoChannelInfo *channel, channelList) { if (channel->channelType() != KoChannelInfo::ALPHA) { m_d->displayPosition[cCount] = channel->displayPosition(); ++cCount; } } Q_ASSERT_X(cCount < 5, "", "unsupported channel count!"); m_d->colorChannelCount = cCount; // TODO: The following is done because the IDs are actually strings. Ideally, in the future, we // refactor everything so that the IDs are actually proper enums or something faster. if (m_d->displayRenderer && (m_d->currentCS->colorDepthId() == Float16BitsColorDepthID || m_d->currentCS->colorDepthId() == Float32BitsColorDepthID || m_d->currentCS->colorDepthId() == Float64BitsColorDepthID) && m_d->currentCS->colorModelId() != LABAColorModelID && m_d->currentCS->colorModelId() != CMYKAColorModelID) { m_d->exposureSupported = true; } else { m_d->exposureSupported = false; } m_d->isRGBA = (m_d->currentCS->colorModelId() == RGBAColorModelID); const KoColorProfile *profile = m_d->currentCS->profile(); m_d->isLinear = (profile && profile->isLinear()); qDeleteAll(children()); m_d->widgetlist.clear(); // TODO: Layout only used for monochrome selector currently, but always present QLayout *layout = new QHBoxLayout; //recreate all the widgets. m_d->model = KisVisualColorSelector::Channel; if (m_d->currentCS->colorChannelCount() == 1) { KisVisualColorSelectorShape *bar; if (m_d->circular==false) { bar = new KisVisualRectangleSelectorShape(this, KisVisualColorSelectorShape::onedimensional, m_d->currentCS, 0, 0,m_d->displayRenderer, 20); bar->setMaximumWidth(width()*0.1); bar->setMaximumHeight(height()); } else { bar = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::onedimensional, m_d->currentCS, 0, 0,m_d->displayRenderer, 20, KisVisualEllipticalSelectorShape::borderMirrored); layout->setMargin(0); } connect(bar, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF))); layout->addWidget(bar); m_d->widgetlist.append(bar); } else if (m_d->currentCS->colorChannelCount() == 3) { KisVisualColorSelector::ColorModel modelS = KisVisualColorSelector::HSV; int channel1 = 0; int channel2 = 1; int channel3 = 2; switch(m_d->acs_config.subTypeParameter) { case KisColorSelectorConfiguration::H: channel1 = 0; break; case KisColorSelectorConfiguration::hsyS: case KisColorSelectorConfiguration::hsiS: case KisColorSelectorConfiguration::hslS: case KisColorSelectorConfiguration::hsvS: channel1 = 1; break; case KisColorSelectorConfiguration::V: case KisColorSelectorConfiguration::L: case KisColorSelectorConfiguration::I: case KisColorSelectorConfiguration::Y: channel1 = 2; break; default: Q_ASSERT_X(false, "", "Invalid acs_config.subTypeParameter"); } switch(m_d->acs_config.mainTypeParameter) { case KisColorSelectorConfiguration::hsySH: modelS = KisVisualColorSelector::HSY; channel2 = 0; channel3 = 1; break; case KisColorSelectorConfiguration::hsiSH: modelS = KisVisualColorSelector::HSI; channel2 = 0; channel3 = 1; break; case KisColorSelectorConfiguration::hslSH: modelS = KisVisualColorSelector::HSL; channel2 = 0; channel3 = 1; break; case KisColorSelectorConfiguration::hsvSH: modelS = KisVisualColorSelector::HSV; channel2 = 0; channel3 = 1; break; case KisColorSelectorConfiguration::YH: modelS = KisVisualColorSelector::HSY; channel2 = 0; channel3 = 2; break; case KisColorSelectorConfiguration::LH: modelS = KisVisualColorSelector::HSL; channel2 = 0; channel3 = 2; break; case KisColorSelectorConfiguration::IH: modelS = KisVisualColorSelector::HSL; channel2 = 0; channel3 = 2; break; case KisColorSelectorConfiguration::VH: modelS = KisVisualColorSelector::HSV; channel2 = 0; channel3 = 2; break; case KisColorSelectorConfiguration::SY: modelS = KisVisualColorSelector::HSY; channel2 = 1; channel3 = 2; break; case KisColorSelectorConfiguration::SI: modelS = KisVisualColorSelector::HSI; channel2 = 1; channel3 = 2; break; case KisColorSelectorConfiguration::SL: modelS = KisVisualColorSelector::HSL; channel2 = 1; channel3 = 2; break; case KisColorSelectorConfiguration::SV: case KisColorSelectorConfiguration::SV2: modelS = KisVisualColorSelector::HSV; channel2 = 1; channel3 = 2; break; default: Q_ASSERT_X(false, "", "Invalid acs_config.mainTypeParameter"); } if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) { modelS = KisVisualColorSelector::HSV; //Triangle only really works in HSV mode. } m_d->model = modelS; KisVisualColorSelectorShape *bar; if (m_d->acs_config.subType == KisColorSelectorConfiguration::Ring) { bar = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::onedimensional, m_d->currentCS, channel1, channel1, m_d->displayRenderer, 20,KisVisualEllipticalSelectorShape::border); } else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular == false) { bar = new KisVisualRectangleSelectorShape(this, KisVisualColorSelectorShape::onedimensional, m_d->currentCS, channel1, channel1, m_d->displayRenderer, 20); } else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular == true) { bar = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::onedimensional, m_d->currentCS, channel1, channel1, m_d->displayRenderer, 20, KisVisualEllipticalSelectorShape::borderMirrored); } else { // Accessing bar below would crash since it's not initialized. // Hopefully this can never happen. warnUI << "Invalid subType, cannot initialize KisVisualColorSelectorShape"; Q_ASSERT_X(false, "", "Invalid subType, cannot initialize KisVisualColorSelectorShape"); return; } m_d->widgetlist.append(bar); KisVisualColorSelectorShape *block; if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) { block = new KisVisualTriangleSelectorShape(this, KisVisualColorSelectorShape::twodimensional, m_d->currentCS, channel2, channel3, m_d->displayRenderer); } else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Square) { block = new KisVisualRectangleSelectorShape(this, KisVisualColorSelectorShape::twodimensional, m_d->currentCS, channel2, channel3, m_d->displayRenderer); } else { block = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::twodimensional, m_d->currentCS, channel2, channel3, m_d->displayRenderer); } connect(bar, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF))); connect(block, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF))); m_d->widgetlist.append(block); } else if (m_d->currentCS->colorChannelCount() == 4) { KisVisualRectangleSelectorShape *block = new KisVisualRectangleSelectorShape(this, KisVisualRectangleSelectorShape::twodimensional, m_d->currentCS, 0, 1); KisVisualRectangleSelectorShape *block2 = new KisVisualRectangleSelectorShape(this, KisVisualRectangleSelectorShape::twodimensional, m_d->currentCS, 2, 3); connect(block, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF))); connect(block2, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF))); m_d->widgetlist.append(block); m_d->widgetlist.append(block2); } this->setLayout(layout); // make sure we call "our" resize function KisVisualColorSelector::resizeEvent(0); // finally recalculate channel values and update widgets if (m_d->displayRenderer) { slotDisplayConfigurationChanged(); } m_d->channelValues = convertKoColorToShapeCoordinates(m_d->currentcolor); Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { shape->setChannelValues(m_d->channelValues, true); // if this widget is currently visible, new children are hidden by default shape->show(); } } void KisVisualColorSelector::setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer) { m_d->displayRenderer = displayRenderer; if (m_d->widgetlist.size()>0) { Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { shape->setDisplayRenderer(displayRenderer); } } connect(m_d->displayRenderer, SIGNAL(displayConfigurationChanged()), SLOT(slotDisplayConfigurationChanged()), Qt::UniqueConnection); slotDisplayConfigurationChanged(); } void KisVisualColorSelector::slotCursorMoved(QPointF pos) { const KisVisualColorSelectorShape *shape = qobject_cast(sender()); Q_ASSERT(shape); QVector channels = shape->getChannels(); m_d->channelValues[channels.at(0)] = pos.x(); - if (shape->getDimensions() == KisVisualColorSelectorShape::twodimensional) - { + if (shape->getDimensions() == KisVisualColorSelectorShape::twodimensional) { m_d->channelValues[channels.at(1)] = pos.y(); } KoColor newColor = convertShapeCoordsToKoColor(m_d->channelValues); - if (newColor != m_d->currentcolor) - { + if (newColor != m_d->currentcolor) { m_d->currentcolor = newColor; - - Q_FOREACH (KisVisualColorSelectorShape *widget, m_d->widgetlist) { - if (widget != shape){ - widget->setChannelValues(m_d->channelValues, false); - } - } emit sigNewColor(m_d->currentcolor); } + Q_FOREACH (KisVisualColorSelectorShape *widget, m_d->widgetlist) { + if (widget != shape){ + widget->setChannelValues(m_d->channelValues, false); + } + } } void KisVisualColorSelector::resizeEvent(QResizeEvent *) { int sizeValue = qMin(width(), height()); int borderWidth = qMax(sizeValue*0.1, 20.0); QRect newrect(0,0, this->geometry().width(), this->geometry().height()); if (!m_d->currentCS) { slotsetColorSpace(m_d->currentcolor.colorSpace()); } if (m_d->currentCS->colorChannelCount()==3) { // set border width first, else the resized painting may have happened already, and we'd have to re-render m_d->widgetlist.at(0)->setBorderWidth(borderWidth); if (m_d->acs_config.subType == KisColorSelectorConfiguration::Ring) { m_d->widgetlist.at(0)->resize(sizeValue,sizeValue); } else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular==false) { m_d->widgetlist.at(0)->resize(borderWidth, sizeValue); } else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular==true) { m_d->widgetlist.at(0)->resize(sizeValue,sizeValue); } if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) { m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForTriangle(newrect)); } else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Square) { m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForSquare(newrect)); } else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Wheel) { m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForCircle(newrect)); } } else if (m_d->currentCS->colorChannelCount() == 4) { int sizeBlock = qMin(width()/2 - 8, height()); m_d->widgetlist.at(0)->setGeometry(0, 0, sizeBlock, sizeBlock); m_d->widgetlist.at(1)->setGeometry(sizeBlock + 8, 0, sizeBlock, sizeBlock); } } diff --git a/libs/widgets/KoFontComboBox.h b/libs/widgets/KoFontComboBox.h deleted file mode 100644 index 5f7d607e1a..0000000000 --- a/libs/widgets/KoFontComboBox.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2014 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 _KO_FONT_COMBO_BOX_H_ -#define _KO_FONT_COMBO_BOX_H_ - -#include -typedef QFontComboBox KoFontComboBox; - -#endif // _KO_FONT_COMBO_BOX_H_ diff --git a/libs/widgets/KoItemToolTip.cpp b/libs/widgets/KoItemToolTip.cpp index 3834ff54bf..0a378d1915 100644 --- a/libs/widgets/KoItemToolTip.cpp +++ b/libs/widgets/KoItemToolTip.cpp @@ -1,141 +1,140 @@ /* Copyright (c) 2006 Gábor Lehel 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 "KoItemToolTip.h" #include #include #include #include #include #include #include #include #include #include #include class Q_DECL_HIDDEN KoItemToolTip::Private { public: QTextDocument *document; QPersistentModelIndex index; QPoint pos; QBasicTimer timer; Private(): document(0) { } }; KoItemToolTip::KoItemToolTip() : d(new Private) { d->document = new QTextDocument(this); setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); QApplication::instance()->installEventFilter(this); } KoItemToolTip::~KoItemToolTip() { delete d; } void KoItemToolTip::showTip(QWidget *widget, const QPoint &pos, const QStyleOptionViewItem &option, const QModelIndex &index) { QTextDocument *doc = createDocument(index); QPoint p = (isVisible() && index == d->index) ? d->pos : pos; if (!isVisible() || index != d->index || doc->toHtml() != d->document->toHtml()) { d->pos = p; d->index = index; delete d->document; d->document = doc; updatePosition(widget, p, option); if (!isVisible()) show(); else update(); d->timer.start(10000, this); } else delete doc; } void KoItemToolTip::updatePosition(QWidget *widget, const QPoint &pos, const QStyleOptionViewItem &option) { const QRect drect = QApplication::desktop()->availableGeometry(widget); const QSize size = sizeHint(); const int width = size.width(), height = size.height(); const QPoint gpos = widget->mapToGlobal(pos); const QRect irect(widget->mapToGlobal(option.rect.topLeft()), option.rect.size()); int y = gpos.y() + 20; if (y + height > drect.bottom()) y = qMax(drect.top(), irect.top() - height); int x; if (gpos.x() + width < drect.right()) x = gpos.x(); else x = qMax(drect.left(), gpos.x() - width); move(x, y); resize(sizeHint()); } QSize KoItemToolTip::sizeHint() const { return d->document->size().toSize(); } void KoItemToolTip::paintEvent(QPaintEvent*) { QPainter p(this); - p.begin(this); d->document->drawContents(&p, rect()); p.drawRect(0, 0, width() - 1, height() - 1); } void KoItemToolTip::timerEvent(QTimerEvent *e) { if (e->timerId() == d->timer.timerId()) { hide(); } } bool KoItemToolTip::eventFilter(QObject *object, QEvent *event) { switch(event->type()) { case QEvent::KeyPress: case QEvent::KeyRelease: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::FocusIn: case QEvent::FocusOut: case QEvent::Enter: case QEvent::Leave: hide(); default: break; } return QFrame::eventFilter(object, event); } diff --git a/libs/widgets/KoSliderCombo.cpp b/libs/widgets/KoSliderCombo.cpp index f3d78735b3..15493157b9 100644 --- a/libs/widgets/KoSliderCombo.cpp +++ b/libs/widgets/KoSliderCombo.cpp @@ -1,291 +1,291 @@ /* This file is part of the KDE project Copyright (c) 2007 C. Boemann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoSliderCombo.h" #include "KoSliderCombo_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KoSliderCombo::KoSliderCombo(QWidget *parent) : QComboBox(parent) ,d(new KoSliderComboPrivate()) { d->thePublic = this; d->minimum = 0.0; d->maximum = 100.0; d->decimals = 2; d->container = new KoSliderComboContainer(this); d->container->setAttribute(Qt::WA_WindowPropagation); QStyleOptionComboBox opt; opt.initFrom(this); // d->container->setFrameStyle(style()->styleHint(QStyle::SH_ComboBox_PopupFrameStyle, &opt, this)); d->slider = new QSlider(Qt::Horizontal); d->slider->setMinimum(0); d->slider->setMaximum(256); d->slider->setPageStep(10); d->slider->setValue(0); // When set to true, causes flicker on Qt 4.6. Any reason to keep it? d->firstShowOfSlider = false; //true; QHBoxLayout * l = new QHBoxLayout(); l->setMargin(2); l->setSpacing(2); l->addWidget(d->slider); d->container->setLayout(l); d->container->resize(200, 30); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); setEditable(true); setEditText(QLocale().toString(0.0, d->decimals)); connect(d->slider, SIGNAL(valueChanged(int)), SLOT(sliderValueChanged(int))); connect(d->slider, SIGNAL(sliderReleased()), SLOT(sliderReleased())); connect(lineEdit(), SIGNAL(editingFinished()), SLOT(lineEditFinished())); } KoSliderCombo::~KoSliderCombo() { delete d; } QSize KoSliderCombo::sizeHint() const { return minimumSizeHint(); } QSize KoSliderCombo::minimumSizeHint() const { QSize sh; const QFontMetrics &fm = fontMetrics(); sh.setWidth(5 * fm.width(QLatin1Char('8'))); sh.setHeight(qMax(fm.lineSpacing(), 14) + 2); // add style and strut values QStyleOptionComboBox opt; opt.initFrom(this); opt.subControls = QStyle::SC_All; opt.editable = true; sh = style()->sizeFromContents(QStyle::CT_ComboBox, &opt, sh, this); - return sh.expandedTo(QApplication::globalStrut()); + return sh; } void KoSliderCombo::KoSliderComboPrivate::showPopup() { if(firstShowOfSlider) { container->show(); //show container a bit early so the slider can be layout'ed firstShowOfSlider = false; } QStyleOptionSlider opt; opt.initFrom(slider); opt.maximum=256; opt.sliderPosition = opt.sliderValue = slider->value(); int hdlPos = thePublic->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle).center().x(); QStyleOptionComboBox optThis; optThis.initFrom(thePublic); optThis.subControls = QStyle::SC_All; optThis.editable = true; int arrowPos = thePublic->style()->subControlRect(QStyle::CC_ComboBox, &optThis, QStyle::SC_ComboBoxArrow).center().x(); QSize popSize = container->size(); QRect popupRect(thePublic->mapToGlobal(QPoint(arrowPos - hdlPos - slider->x(), thePublic->size().height())), popSize); // Make sure the popup is not drawn outside the screen area QRect screenRect = QApplication::desktop()->availableGeometry(thePublic); if (popupRect.right() > screenRect.right()) popupRect.translate(screenRect.right() - popupRect.right(), 0); if (popupRect.left() < screenRect.left()) popupRect.translate(screenRect.left() - popupRect.left(), 0); if (popupRect.bottom() > screenRect.bottom()) popupRect.translate(0, -(thePublic->height() + container->height())); container->setGeometry(popupRect); container->raise(); container->show(); slider->setFocus(); } void KoSliderCombo::KoSliderComboPrivate::hidePopup() { container->hide(); } void KoSliderCombo::hideEvent(QHideEvent *) { d->hidePopup(); } void KoSliderCombo::changeEvent(QEvent *e) { switch (e->type()) { case QEvent::EnabledChange: if (!isEnabled()) d->hidePopup(); break; case QEvent::PaletteChange: d->container->setPalette(palette()); break; default: break; } QComboBox::changeEvent(e); } void KoSliderCombo::paintEvent(QPaintEvent *) { QStylePainter gc(this); gc.setPen(palette().color(QPalette::Text)); QStyleOptionComboBox opt; opt.initFrom(this); opt.subControls = QStyle::SC_All; opt.editable = true; gc.drawComplexControl(QStyle::CC_ComboBox, opt); gc.drawControl(QStyle::CE_ComboBoxLabel, opt); } void KoSliderCombo::mousePressEvent(QMouseEvent *e) { QStyleOptionComboBox opt; opt.initFrom(this); opt.subControls = QStyle::SC_All; opt.editable = true; QStyle::SubControl sc = style()->hitTestComplexControl(QStyle::CC_ComboBox, &opt, e->pos(), this); if (sc == QStyle::SC_ComboBoxArrow && !d->container->isVisible()) { d->showPopup(); } else QComboBox::mousePressEvent(e); } void KoSliderCombo::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Up) setValue(value() + d->slider->singleStep() * (maximum() - minimum()) / 256 + 0.5); else if (e->key() == Qt::Key_Down) setValue(value() - d->slider->singleStep() * (maximum() - minimum()) / 256 - 0.5); else QComboBox::keyPressEvent(e); } void KoSliderCombo::wheelEvent(QWheelEvent *e) { if (e->delta() > 0) setValue(value() + d->slider->singleStep() * (maximum() - minimum()) / 256 + 0.5); else setValue(value() - d->slider->singleStep() * (maximum() - minimum()) / 256 - 0.5); } void KoSliderCombo::KoSliderComboPrivate::lineEditFinished() { qreal value = QLocale().toDouble(thePublic->currentText()); slider->blockSignals(true); slider->setValue(int((value - minimum) * 256 / (maximum - minimum) + 0.5)); slider->blockSignals(false); emit thePublic->valueChanged(value, true); } void KoSliderCombo::KoSliderComboPrivate::sliderValueChanged(int slidervalue) { thePublic->setEditText(QLocale().toString(minimum + (maximum - minimum)*slidervalue/256, decimals)); qreal value = QLocale().toDouble(thePublic->currentText()); emit thePublic->valueChanged(value, false); } void KoSliderCombo::KoSliderComboPrivate::sliderReleased() { qreal value = QLocale().toDouble(thePublic->currentText()); emit thePublic->valueChanged(value, true); } qreal KoSliderCombo::maximum() const { return d->maximum; } qreal KoSliderCombo::minimum() const { return d->minimum; } qreal KoSliderCombo::decimals() const { return d->decimals; } qreal KoSliderCombo::value() const { return QLocale().toDouble(currentText()); } void KoSliderCombo::setDecimals(int dec) { d->decimals = dec; if (dec == 0) lineEdit()->setValidator(new QIntValidator(this)); else lineEdit()->setValidator(new QDoubleValidator(this)); } void KoSliderCombo::setMinimum(qreal min) { d->minimum = min; } void KoSliderCombo::setMaximum(qreal max) { d->maximum = max; } void KoSliderCombo::setValue(qreal value) { if(value < d->minimum) value = d->minimum; if(value > d->maximum) value = d->maximum; setEditText(QLocale().toString(value, d->decimals)); d->slider->blockSignals(true); d->slider->setValue(int((value - d->minimum) * 256 / (d->maximum - d->minimum) + 0.5)); d->slider->blockSignals(false); emit valueChanged(value, true); } //have to include this because of Q_PRIVATE_SLOT #include diff --git a/libs/widgets/KoToolBox.cpp b/libs/widgets/KoToolBox.cpp index efeaa4090f..360aa26f1d 100644 --- a/libs/widgets/KoToolBox.cpp +++ b/libs/widgets/KoToolBox.cpp @@ -1,342 +1,338 @@ /* * Copyright (c) 2005-2009 Thomas Zander * Copyright (c) 2009 Peter Simonsson * Copyright (c) 2010 Cyrille Berger * * 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 "KoToolBox_p.h" #include "KoToolBoxLayout_p.h" #include "KoToolBoxButton_p.h" +#include "kis_assert.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BUTTON_MARGIN 10 static int buttonSize(int screen) { + KIS_ASSERT_RECOVER_RETURN_VALUE(screen < QGuiApplication::screens().size() && screen >= 0, 16); + QRect rc = QGuiApplication::screens().at(screen)->availableGeometry(); if (rc.width() <= 1024) { return 12; } else if (rc.width() <= 1377) { return 14; } else if (rc.width() <= 1920 ) { return 16; } else { return 22; } + } class KoToolBox::Private { public: - Private() - : layout(0) - , buttonGroup(0) - , floating(false) - , contextSize(0) - { - } - void addSection(Section *section, const QString &name); QList buttons; QMap sections; - KoToolBoxLayout *layout; - QButtonGroup *buttonGroup; + KoToolBoxLayout *layout {0}; + QButtonGroup *buttonGroup {0}; QHash visibilityCodes; - bool floating; + bool floating {false}; QMap contextIconSizes; - QMenu* contextSize; - Qt::Orientation orientation; + QMenu *contextSize {0}; + Qt::Orientation orientation {Qt::Vertical}; }; void KoToolBox::Private::addSection(Section *section, const QString &name) { section->setName(name); layout->addSection(section); sections.insert(name, section); } KoToolBox::KoToolBox() : d(new Private) { d->layout = new KoToolBoxLayout(this); // add defaults d->addSection(new Section(this), "main"); d->addSection(new Section(this), "dynamic"); d->buttonGroup = new QButtonGroup(this); setLayout(d->layout); Q_FOREACH (KoToolAction *toolAction, KoToolManager::instance()->toolActionList()) { addButton(toolAction); } // Update visibility of buttons setButtonsVisible(QList()); connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), this, SLOT(setActiveTool(KoCanvasController*,int))); connect(KoToolManager::instance(), SIGNAL(currentLayerChanged(const KoCanvasController*,const KoShapeLayer*)), this, SLOT(setCurrentLayer(const KoCanvasController*,const KoShapeLayer*))); connect(KoToolManager::instance(), SIGNAL(toolCodesSelected(QList)), this, SLOT(setButtonsVisible(QList))); connect(KoToolManager::instance(), SIGNAL(addedTool(KoToolAction*,KoCanvasController*)), this, SLOT(toolAdded(KoToolAction*,KoCanvasController*))); } KoToolBox::~KoToolBox() { delete d; } void KoToolBox::addButton(KoToolAction *toolAction) { KoToolBoxButton *button = new KoToolBoxButton(toolAction, this); d->buttons << button; int toolbuttonSize = buttonSize(qApp->desktop()->screenNumber(this)); KConfigGroup cfg = KSharedConfig::openConfig()->group("KoToolBox"); int iconSize = cfg.readEntry("iconSize", toolbuttonSize); button->setIconSize(QSize(iconSize, iconSize)); foreach (Section *section, d->sections.values()) { section->setButtonSize(QSize(iconSize + BUTTON_MARGIN, iconSize + BUTTON_MARGIN)); } QString sectionToBeAddedTo; const QString section = toolAction->section(); if (section.contains(qApp->applicationName())) { sectionToBeAddedTo = "main"; } else if (section.contains("main")) { sectionToBeAddedTo = "main"; } else if (section.contains("dynamic")) { sectionToBeAddedTo = "dynamic"; } else { sectionToBeAddedTo = section; } Section *sectionWidget = d->sections.value(sectionToBeAddedTo); if (sectionWidget == 0) { sectionWidget = new Section(this); d->addSection(sectionWidget, sectionToBeAddedTo); } sectionWidget->addButton(button, toolAction->priority()); d->buttonGroup->addButton(button, toolAction->buttonGroupId()); d->visibilityCodes.insert(button, toolAction->visibilityCode()); } void KoToolBox::setActiveTool(KoCanvasController *canvas, int id) { Q_UNUSED(canvas); QAbstractButton *button = d->buttonGroup->button(id); if (button) { button->setChecked(true); (qobject_cast(button))->setHighlightColor(); } else { warnWidgets << "KoToolBox::setActiveTool(" << id << "): no such button found"; } } void KoToolBox::setButtonsVisible(const QList &codes) { Q_FOREACH (QToolButton *button, d->visibilityCodes.keys()) { QString code = d->visibilityCodes.value(button); if (code.startsWith(QLatin1String("flake/"))) { continue; } if (code.endsWith( QLatin1String( "/always"))) { button->setVisible(true); button->setEnabled(true); } else if (code.isEmpty()) { button->setVisible(true); button->setEnabled( codes.count() != 0 ); } else { button->setVisible( codes.contains(code) ); } } layout()->invalidate(); update(); } void KoToolBox::setCurrentLayer(const KoCanvasController *canvas, const KoShapeLayer *layer) { Q_UNUSED(canvas); const bool enabled = layer == 0 || (layer->isShapeEditable() && layer->isVisible()); foreach (QToolButton *button, d->visibilityCodes.keys()) { if (d->visibilityCodes[button].endsWith( QLatin1String( "/always") ) ) { continue; } button->setEnabled(enabled); } } void KoToolBox::paintEvent(QPaintEvent *) { QPainter painter(this); const QList sections = d->sections.values(); QList::const_iterator iterator = sections.begin(); int halfSpacing = layout()->spacing(); if (halfSpacing > 0) { halfSpacing /= 2; } while(iterator != sections.end()) { Section *section = *iterator; QStyleOption styleoption; styleoption.palette = palette(); if (section->separators() & Section::SeparatorTop) { int y = section->y() - halfSpacing; styleoption.state = QStyle::State_None; styleoption.rect = QRect(section->x(), y-1, section->width(), 2); style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &styleoption, &painter); } if (section->separators() & Section::SeparatorLeft) { int x = section->x() - halfSpacing; styleoption.state = QStyle::State_Horizontal; styleoption.rect = QRect(x-1, section->y(), 2, section->height()); style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &styleoption, &painter); } ++iterator; } painter.end(); } void KoToolBox::setOrientation(Qt::Orientation orientation) { d->orientation = orientation; d->layout->setOrientation(orientation); QTimer::singleShot(0, this, SLOT(update())); Q_FOREACH (Section* section, d->sections) { section->setOrientation(orientation); } } void KoToolBox::setFloating(bool v) { d->floating = v; } void KoToolBox::toolAdded(KoToolAction *toolAction, KoCanvasController *canvas) { Q_UNUSED(canvas); addButton(toolAction); setButtonsVisible(QList()); } void KoToolBox::slotContextIconSize() { QAction* action = qobject_cast(sender()); if (action && d->contextIconSizes.contains(action)) { const int iconSize = d->contextIconSizes.value(action); KConfigGroup cfg = KSharedConfig::openConfig()->group("KoToolBox"); cfg.writeEntry("iconSize", iconSize); Q_FOREACH (QToolButton *button, d->buttons) { button->setIconSize(QSize(iconSize, iconSize)); } Q_FOREACH (Section *section, d->sections.values()) { section->setButtonSize(QSize(iconSize + BUTTON_MARGIN, iconSize + BUTTON_MARGIN)); } } } void KoToolBox::contextMenuEvent(QContextMenuEvent *event) { int toolbuttonSize = buttonSize(qApp->desktop()->screenNumber(this)); if (!d->contextSize) { d->contextSize = new QMenu(i18n("Icon Size"), this); d->contextIconSizes.insert(d->contextSize->addAction(i18nc("@item:inmenu Icon size", "Default"), this, SLOT(slotContextIconSize())), toolbuttonSize); QList sizes; sizes << 12 << 14 << 16 << 22 << 32 << 48 << 64; //<< 96 << 128 << 192 << 256; Q_FOREACH (int i, sizes) { d->contextIconSizes.insert(d->contextSize->addAction(i18n("%1x%2", i, i), this, SLOT(slotContextIconSize())), i); } QActionGroup *sizeGroup = new QActionGroup(d->contextSize); foreach (QAction *action, d->contextSize->actions()) { action->setActionGroup(sizeGroup); action->setCheckable(true); } } KConfigGroup cfg = KSharedConfig::openConfig()->group("KoToolBox"); toolbuttonSize = cfg.readEntry("iconSize", toolbuttonSize); QMapIterator< QAction*, int > it = d->contextIconSizes; while (it.hasNext()) { it.next(); if (it.value() == toolbuttonSize) { it.key()->setChecked(true); break; } } d->contextSize->exec(event->globalPos()); } KoToolBoxLayout *KoToolBox::toolBoxLayout() const { return d->layout; } #include "moc_KoToolBoxScrollArea_p.cpp" diff --git a/libs/widgets/kis_color_button.cpp b/libs/widgets/kis_color_button.cpp index 2e60eb5e47..49ba3729ce 100644 --- a/libs/widgets/kis_color_button.cpp +++ b/libs/widgets/kis_color_button.cpp @@ -1,387 +1,385 @@ /* This file is part of the KDE libraries Copyright (C) 1997 Martin Jones (mjones@kde.org) Copyright (C) 1999 Cristian Tibirna (ctibirna@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_color_button.h" #include #include #include #include #include #include #include #include #include #include #include #include #include class KisColorButton::KisColorButtonPrivate { public: KisColorButtonPrivate(KisColorButton *q); ~KisColorButtonPrivate() { if (dialogPtr) { dialogPtr.data()->close(); } } void _k_chooseColor(); void _k_colorChosen(); KisColorButton *q; KoColor m_defaultColor; bool m_bdefaultColor : 1; bool m_alphaChannel : 1; bool m_palette : 1; KoColor col; QPoint mPos; #ifndef Q_OS_MACOS QPointer dialogPtr; #else QPointer dialogPtr; #endif void initStyleOption(QStyleOptionButton *opt) const; }; ///////////////////////////////////////////////////////////////////// // Functions duplicated from KColorMimeData // Should be kept in sync void _k_populateMimeData(QMimeData *mimeData, const KoColor &color) { mimeData->setColorData(color.toQColor()); mimeData->setText(color.toQColor().name()); } bool _k_canDecode(const QMimeData *mimeData) { if (mimeData->hasColor()) { return true; } if (mimeData->hasText()) { const QString colorName = mimeData->text(); if ((colorName.length() >= 4) && (colorName[0] == QLatin1Char('#'))) { return true; } } return false; } QColor _k_fromMimeData(const QMimeData *mimeData) { if (mimeData->hasColor()) { return mimeData->colorData().value(); } if (_k_canDecode(mimeData)) { return QColor(mimeData->text()); } return QColor(); } QDrag *_k_createDrag(const KoColor &color, QObject *dragsource) { QDrag *drag = new QDrag(dragsource); QMimeData *mime = new QMimeData; _k_populateMimeData(mime, color); drag->setMimeData(mime); QPixmap colorpix(25, 20); colorpix.fill(color.toQColor()); QPainter p(&colorpix); p.setPen(Qt::black); p.drawRect(0, 0, 24, 19); p.end(); drag->setPixmap(colorpix); drag->setHotSpot(QPoint(-5, -7)); return drag; } ///////////////////////////////////////////////////////////////////// KisColorButton::KisColorButtonPrivate::KisColorButtonPrivate(KisColorButton *q) : q(q) { m_bdefaultColor = false; m_alphaChannel = false; m_palette = true; q->setAcceptDrops(true); connect(q, SIGNAL(clicked()), q, SLOT(_k_chooseColor())); } KisColorButton::KisColorButton(QWidget *parent) : QPushButton(parent) , d(new KisColorButtonPrivate(this)) { } KisColorButton::KisColorButton(const KoColor &c, QWidget *parent) : QPushButton(parent) , d(new KisColorButtonPrivate(this)) { d->col = c; } KisColorButton::KisColorButton(const KoColor &c, const KoColor &defaultColor, QWidget *parent) : QPushButton(parent) , d(new KisColorButtonPrivate(this)) { d->col = c; setDefaultColor(defaultColor); } KisColorButton::~KisColorButton() { delete d; } KoColor KisColorButton::color() const { return d->col; } void KisColorButton::setColor(const KoColor &c) { d->col = c; update(); emit changed(d->col); } void KisColorButton::setAlphaChannelEnabled(bool alpha) { d->m_alphaChannel = alpha; } bool KisColorButton::isAlphaChannelEnabled() const { return d->m_alphaChannel; } void KisColorButton::setPaletteViewEnabled(bool enable) { d->m_palette = enable; } bool KisColorButton::paletteViewEnabled() const { return d->m_palette; } KoColor KisColorButton::defaultColor() const { return d->m_defaultColor; } void KisColorButton::setDefaultColor(const KoColor &c) { d->m_bdefaultColor = true; d->m_defaultColor = c; } void KisColorButton::KisColorButtonPrivate::initStyleOption(QStyleOptionButton *opt) const { opt->initFrom(q); opt->state |= q->isDown() ? QStyle::State_Sunken : QStyle::State_Raised; opt->features = QStyleOptionButton::None; if (q->isDefault()) { opt->features |= QStyleOptionButton::DefaultButton; } opt->text.clear(); opt->icon = QIcon(); } void KisColorButton::paintEvent(QPaintEvent *) { QPainter painter(this); QStyle *style = QWidget::style(); //First, we need to draw the bevel. QStyleOptionButton butOpt; d->initStyleOption(&butOpt); style->drawControl(QStyle::CE_PushButtonBevel, &butOpt, &painter, this); //OK, now we can muck around with drawing out pretty little color box //First, sort out where it goes QRect labelRect = style->subElementRect(QStyle::SE_PushButtonContents, &butOpt, this); int shift = style->pixelMetric(QStyle::PM_ButtonMargin, &butOpt, this) / 2; labelRect.adjust(shift, shift, -shift, -shift); int x, y, w, h; labelRect.getRect(&x, &y, &w, &h); if (isChecked() || isDown()) { x += style->pixelMetric(QStyle::PM_ButtonShiftHorizontal, &butOpt, this); y += style->pixelMetric(QStyle::PM_ButtonShiftVertical, &butOpt, this); } QColor fillCol = isEnabled() ? d->col.toQColor() : palette().color(backgroundRole()); qDrawShadePanel(&painter, x, y, w, h, palette(), true, 1, NULL); if (fillCol.isValid()) { const QRect rect(x + 1, y + 1, w - 2, h - 2); if (fillCol.alpha() < 255) { QPixmap chessboardPattern(16, 16); QPainter patternPainter(&chessboardPattern); patternPainter.fillRect(0, 0, 8, 8, Qt::black); patternPainter.fillRect(8, 8, 8, 8, Qt::black); patternPainter.fillRect(0, 8, 8, 8, Qt::white); patternPainter.fillRect(8, 0, 8, 8, Qt::white); patternPainter.end(); painter.fillRect(rect, QBrush(chessboardPattern)); } painter.fillRect(rect, fillCol); } if (hasFocus()) { QRect focusRect = style->subElementRect(QStyle::SE_PushButtonFocusRect, &butOpt, this); QStyleOptionFocusRect focusOpt; focusOpt.init(this); focusOpt.rect = focusRect; focusOpt.backgroundColor = palette().window().color(); style->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpt, &painter, this); } } QSize KisColorButton::sizeHint() const { QStyleOptionButton opt; d->initStyleOption(&opt); - return style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(40, 15), this). - expandedTo(QApplication::globalStrut()); + return style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(40, 15), this); } QSize KisColorButton::minimumSizeHint() const { QStyleOptionButton opt; d->initStyleOption(&opt); - return style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(3, 3), this). - expandedTo(QApplication::globalStrut()); + return style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(3, 3), this); } void KisColorButton::dragEnterEvent(QDragEnterEvent *event) { event->setAccepted(_k_canDecode(event->mimeData()) && isEnabled()); } void KisColorButton::dropEvent(QDropEvent *event) { QColor c = _k_fromMimeData(event->mimeData()); if (c.isValid()) { KoColor col; col.fromQColor(c); setColor(col); } } void KisColorButton::keyPressEvent(QKeyEvent *e) { int key = e->key() | e->modifiers(); if (QKeySequence::keyBindings(QKeySequence::Copy).contains(key)) { QMimeData *mime = new QMimeData; _k_populateMimeData(mime, color()); QApplication::clipboard()->setMimeData(mime, QClipboard::Clipboard); } else if (QKeySequence::keyBindings(QKeySequence::Paste).contains(key)) { QColor color = _k_fromMimeData(QApplication::clipboard()->mimeData(QClipboard::Clipboard)); KoColor col; col.fromQColor(color); setColor(col); } else { QPushButton::keyPressEvent(e); } } void KisColorButton::mousePressEvent(QMouseEvent *e) { d->mPos = e->pos(); QPushButton::mousePressEvent(e); } void KisColorButton::mouseMoveEvent(QMouseEvent *e) { if ((e->buttons() & Qt::LeftButton) && (e->pos() - d->mPos).manhattanLength() > QApplication::startDragDistance()) { _k_createDrag(color(), this)->exec(); setDown(false); } } void KisColorButton::KisColorButtonPrivate::_k_chooseColor() { #ifndef Q_OS_MACOS KisDlgInternalColorSelector *dialog = dialogPtr.data(); #else QColorDialog *dialog = dialogPtr.data(); #endif if (dialog) { #ifndef Q_OS_MACOS dialog->setPreviousColor(q->color()); #else dialog->setCurrentColor(q->color().toQColor()); #endif dialog->show(); dialog->raise(); dialog->activateWindow(); return; } KisDlgInternalColorSelector::Config cfg; cfg.paletteBox = q->paletteViewEnabled(); #ifndef Q_OS_MACOS dialog = new KisDlgInternalColorSelector(q, q->color(), cfg, i18n("Choose a color")); #else dialog = new QColorDialog(q); dialog->setOption(QColorDialog::ShowAlphaChannel, m_alphaChannel); #endif dialog->setAttribute(Qt::WA_DeleteOnClose); connect(dialog, SIGNAL(accepted()), q, SLOT(_k_colorChosen())); dialogPtr = dialog; #ifndef Q_OS_MACOS dialog->setPreviousColor(q->color()); #else dialog->setCurrentColor(q->color().toQColor()); #endif dialog->show(); } void KisColorButton::KisColorButtonPrivate::_k_colorChosen() { #ifndef Q_OS_MACOS KisDlgInternalColorSelector *dialog = dialogPtr.data(); #else QColorDialog *dialog = dialogPtr.data(); #endif if (!dialog) { return; } #ifndef Q_OS_MACOS q->setColor(dialog->getCurrentColor()); #else KoColor c; c.fromQColor(dialog->currentColor()); q->setColor(c); #endif } #include "moc_kis_color_button.cpp" diff --git a/libs/widgets/kis_color_input.cpp b/libs/widgets/kis_color_input.cpp index 01b7b0dd74..8687176162 100644 --- a/libs/widgets/kis_color_input.cpp +++ b/libs/widgets/kis_color_input.cpp @@ -1,436 +1,439 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2011 Sven Langkamp * Copyright (c) 2015 Moritz Molch * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_color_input.h" #include #ifdef HAVE_OPENEXR #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_double_parse_spin_box.h" #include "kis_int_parse_spin_box.h" KisColorInput::KisColorInput(QWidget* parent, const KoChannelInfo* channelInfo, KoColor* color, KoColorDisplayRendererInterface *displayRenderer, bool usePercentage) : QWidget(parent), m_channelInfo(channelInfo), m_color(color), m_displayRenderer(displayRenderer), m_usePercentage(usePercentage) { } void KisColorInput::init() { QHBoxLayout* m_layout = new QHBoxLayout(this); m_layout->setContentsMargins(0,0,0,0); m_layout->setSpacing(1); QLabel* m_label = new QLabel(i18n("%1:", m_channelInfo->name()), this); m_layout->addWidget(m_label); m_colorSlider = new KoColorSlider(Qt::Horizontal, this, m_displayRenderer); m_colorSlider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_layout->addWidget(m_colorSlider); QWidget* m_input = createInput(); m_colorSlider->setFixedHeight(m_input->sizeHint().height()); m_layout->addWidget(m_input); } KisIntegerColorInput::KisIntegerColorInput(QWidget* parent, const KoChannelInfo* channelInfo, KoColor* color, KoColorDisplayRendererInterface *displayRenderer, bool usePercentage) : KisColorInput(parent, channelInfo, color, displayRenderer, usePercentage) { init(); } void KisIntegerColorInput::setValue(int v) { quint8* data = m_color->data() + m_channelInfo->pos(); switch (m_channelInfo->channelValueType()) { case KoChannelInfo::UINT8: *(reinterpret_cast(data)) = v; break; case KoChannelInfo::UINT16: *(reinterpret_cast(data)) = v; break; case KoChannelInfo::UINT32: *(reinterpret_cast(data)) = v; break; default: Q_ASSERT(false); } emit(updated()); } void KisIntegerColorInput::update() { KoColor min = *m_color; KoColor max = *m_color; quint8* data = m_color->data() + m_channelInfo->pos(); quint8* dataMin = min.data() + m_channelInfo->pos(); quint8* dataMax = max.data() + m_channelInfo->pos(); m_intNumInput->blockSignals(true); m_colorSlider->blockSignals(true); switch (m_channelInfo->channelValueType()) { case KoChannelInfo::UINT8: if (m_usePercentage) { m_intNumInput->setMaximum(100); m_intNumInput->setValue(round(*(reinterpret_cast(data))*1.0 / 255.0 * 100.0)); } else { m_intNumInput->setMaximum(0xFF); m_intNumInput->setValue(*(reinterpret_cast(data))); } m_colorSlider->setValue(*(reinterpret_cast(data))); *(reinterpret_cast(dataMin)) = 0x0; *(reinterpret_cast(dataMax)) = 0xFF; break; case KoChannelInfo::UINT16: if (m_usePercentage) { m_intNumInput->setMaximum(100); m_intNumInput->setValue(round(*(reinterpret_cast(data))*1.0 / 65535.0 * 100.0)); } else { m_intNumInput->setMaximum(0xFFFF); m_intNumInput->setValue(*(reinterpret_cast(data))); } m_colorSlider->setValue(*(reinterpret_cast(data))); *(reinterpret_cast(dataMin)) = 0x0; *(reinterpret_cast(dataMax)) = 0xFFFF; break; case KoChannelInfo::UINT32: if (m_usePercentage) { m_intNumInput->setMaximum(100); m_intNumInput->setValue(round(*(reinterpret_cast(data))*1.0 / 4294967295.0 * 100.0)); } else { m_intNumInput->setMaximum(0xFFFF); m_intNumInput->setValue(*(reinterpret_cast(data))); } m_colorSlider->setValue(*(reinterpret_cast(data))); *(reinterpret_cast(dataMin)) = 0x0; *(reinterpret_cast(dataMax)) = 0xFFFFFFFF; break; default: Q_ASSERT(false); } m_colorSlider->setColors(min, max); m_intNumInput->blockSignals(false); m_colorSlider->blockSignals(false); } QWidget* KisIntegerColorInput::createInput() { m_intNumInput = new KisIntParseSpinBox(this); m_intNumInput->setMinimum(0); m_colorSlider->setMinimum(0); if (m_usePercentage) { m_intNumInput->setSuffix(i18n("%")); } else { m_intNumInput->setSuffix(""); } switch (m_channelInfo->channelValueType()) { case KoChannelInfo::UINT8: if (m_usePercentage) { m_intNumInput->setMaximum(100); } else { m_intNumInput->setMaximum(0xFF); } m_colorSlider->setMaximum(0xFF); break; case KoChannelInfo::UINT16: if (m_usePercentage) { m_intNumInput->setMaximum(100); } else { m_intNumInput->setMaximum(0xFFFF); } m_colorSlider->setMaximum(0xFFFF); break; case KoChannelInfo::UINT32: if (m_usePercentage) { m_intNumInput->setMaximum(100); } else { m_intNumInput->setMaximum(0xFFFFFFFF); } m_colorSlider->setMaximum(0xFFFFFFFF); break; default: Q_ASSERT(false); } connect(m_colorSlider, SIGNAL(valueChanged(int)), this, SLOT(onColorSliderChanged(int))); connect(m_intNumInput, SIGNAL(valueChanged(int)), this, SLOT(onNumInputChanged(int))); return m_intNumInput; } void KisIntegerColorInput::setPercentageWise(bool val) { m_usePercentage = val; if (m_usePercentage) { m_intNumInput->setSuffix(i18n("%")); } else { m_intNumInput->setSuffix(""); } } void KisIntegerColorInput::onColorSliderChanged(int val) { m_intNumInput->blockSignals(true); if (m_usePercentage) { switch (m_channelInfo->channelValueType()) { case KoChannelInfo::UINT8: m_intNumInput->setValue(round((val*1.0) / 255.0 * 100.0)); break; case KoChannelInfo::UINT16: m_intNumInput->setValue(round((val*1.0) / 65535.0 * 100.0)); break; case KoChannelInfo::UINT32: m_intNumInput->setValue(round((val*1.0) / 4294967295.0 * 100.0)); break; default: Q_ASSERT(false); } } else { m_intNumInput->setValue(val); } m_intNumInput->blockSignals(false); setValue(val); } void KisIntegerColorInput::onNumInputChanged(int val) { m_colorSlider->blockSignals(true); if (m_usePercentage) { switch (m_channelInfo->channelValueType()) { case KoChannelInfo::UINT8: m_colorSlider->setValue((val*1.0)/100.0 * 255.0); m_colorSlider->blockSignals(false); setValue((val*1.0)/100.0 * 255.0); break; case KoChannelInfo::UINT16: m_colorSlider->setValue((val*1.0)/100.0 * 65535.0); m_colorSlider->blockSignals(false); setValue((val*1.0)/100.0 * 65535.0); break; case KoChannelInfo::UINT32: m_colorSlider->setValue((val*1.0)/100.0 * 4294967295.0); m_colorSlider->blockSignals(false); setValue((val*1.0)/100.0 * 4294967295.0); break; default: Q_ASSERT(false); } } else { m_colorSlider->setValue(val); m_colorSlider->blockSignals(false); setValue(val); } } KisFloatColorInput::KisFloatColorInput(QWidget* parent, const KoChannelInfo* channelInfo, KoColor* color, KoColorDisplayRendererInterface *displayRenderer, bool usePercentage) : KisColorInput(parent, channelInfo, color, displayRenderer, usePercentage) { init(); } void KisFloatColorInput::setValue(double v) { quint8* data = m_color->data() + m_channelInfo->pos(); switch (m_channelInfo->channelValueType()) { #ifdef HAVE_OPENEXR case KoChannelInfo::FLOAT16: *(reinterpret_cast(data)) = v; break; #endif case KoChannelInfo::FLOAT32: *(reinterpret_cast(data)) = v; break; default: Q_ASSERT(false); } emit(updated()); } QWidget* KisFloatColorInput::createInput() { m_dblNumInput = new KisDoubleParseSpinBox(this); m_dblNumInput->setMinimum(0); m_dblNumInput->setMaximum(1.0); connect(m_colorSlider, SIGNAL(valueChanged(int)), this, SLOT(sliderChanged(int))); connect(m_dblNumInput, SIGNAL(valueChanged(double)), this, SLOT(setValue(double))); m_dblNumInput->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); m_dblNumInput->setMinimumWidth(60); m_dblNumInput->setMaximumWidth(60); quint8* data = m_color->data() + m_channelInfo->pos(); qreal value = 1.0; switch (m_channelInfo->channelValueType()) { #ifdef HAVE_OPENEXR case KoChannelInfo::FLOAT16: value = *(reinterpret_cast(data)); break; #endif case KoChannelInfo::FLOAT32: value = *(reinterpret_cast(data)); break; default: Q_ASSERT(false); } m_dblNumInput->setValue(value); return m_dblNumInput; } void KisFloatColorInput::sliderChanged(int i) { const qreal floatRange = m_maxValue - m_minValue; m_dblNumInput->setValue(m_minValue + (i / 255.0) * floatRange); } void KisFloatColorInput::update() { KoColor min = *m_color; KoColor max = *m_color; quint8* data = m_color->data() + m_channelInfo->pos(); quint8* dataMin = min.data() + m_channelInfo->pos(); quint8* dataMax = max.data() + m_channelInfo->pos(); qreal value = 1.0; m_minValue = m_displayRenderer->minVisibleFloatValue(m_channelInfo); m_maxValue = m_displayRenderer->maxVisibleFloatValue(m_channelInfo); + m_dblNumInput->blockSignals(true); m_colorSlider->blockSignals(true); switch (m_channelInfo->channelValueType()) { #ifdef HAVE_OPENEXR case KoChannelInfo::FLOAT16: value = *(reinterpret_cast(data)); m_minValue = qMin(value, m_minValue); m_maxValue = qMax(value, m_maxValue); *(reinterpret_cast(dataMin)) = m_minValue; *(reinterpret_cast(dataMax)) = m_maxValue; break; #endif case KoChannelInfo::FLOAT32: value = *(reinterpret_cast(data)); m_minValue = qMin(value, m_minValue); m_maxValue = qMax(value, m_maxValue); *(reinterpret_cast(dataMin)) = m_minValue; *(reinterpret_cast(dataMax)) = m_maxValue; break; default: Q_ASSERT(false); } m_dblNumInput->setMinimum(m_minValue); m_dblNumInput->setMaximum(m_maxValue); // ensure at least 3 significant digits are always shown int newPrecision = 2 + qMax(qreal(0.0), std::ceil(-std::log10(m_maxValue))); if (newPrecision != m_dblNumInput->decimals()) { m_dblNumInput->setDecimals(newPrecision); m_dblNumInput->updateGeometry(); } + m_dblNumInput->setValue(value); m_colorSlider->setColors(min, max); const qreal floatRange = m_maxValue - m_minValue; m_colorSlider->setValue((value - m_minValue) / floatRange * 255); + m_dblNumInput->blockSignals(false); m_colorSlider->blockSignals(false); } KisHexColorInput::KisHexColorInput(QWidget* parent, KoColor* color, KoColorDisplayRendererInterface *displayRenderer, bool usePercentage) : KisColorInput(parent, 0, color, displayRenderer, usePercentage) { QHBoxLayout* m_layout = new QHBoxLayout(this); m_layout->setContentsMargins(0,0,0,0); m_layout->setSpacing(1); QLabel* m_label = new QLabel(i18n("Color name:"), this); m_label->setMinimumWidth(50); m_layout->addWidget(m_label); QWidget* m_input = createInput(); m_input->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); m_layout->addWidget(m_input); } void KisHexColorInput::setValue() { QString valueString = m_hexInput->text(); valueString.remove(QChar('#')); QList channels = m_color->colorSpace()->channels(); channels = KoChannelInfo::displayOrderSorted(channels); Q_FOREACH (KoChannelInfo* channel, channels) { if (channel->channelType() == KoChannelInfo::COLOR) { Q_ASSERT(channel->channelValueType() == KoChannelInfo::UINT8); quint8* data = m_color->data() + channel->pos(); int value = valueString.left(2).toInt(0, 16); *(reinterpret_cast(data)) = value; valueString.remove(0, 2); } } emit(updated()); } void KisHexColorInput::update() { QString hexString("#"); QList channels = m_color->colorSpace()->channels(); channels = KoChannelInfo::displayOrderSorted(channels); Q_FOREACH (KoChannelInfo* channel, channels) { if (channel->channelType() == KoChannelInfo::COLOR) { Q_ASSERT(channel->channelValueType() == KoChannelInfo::UINT8); quint8* data = m_color->data() + channel->pos(); hexString.append(QString("%1").arg(*(reinterpret_cast(data)), 2, 16, QChar('0'))); } } m_hexInput->setText(hexString); } QWidget* KisHexColorInput::createInput() { m_hexInput = new QLineEdit(this); m_hexInput->setAlignment(Qt::AlignRight); int digits = 2*m_color->colorSpace()->colorChannelCount(); QString pattern = QString("#?[a-fA-F0-9]{%1,%2}").arg(digits).arg(digits); m_hexInput->setValidator(new QRegExpValidator(QRegExp(pattern), this)); connect(m_hexInput, SIGNAL(editingFinished()), this, SLOT(setValue())); return m_hexInput; } diff --git a/libs/widgets/kis_spinbox_color_selector.cpp b/libs/widgets/kis_spinbox_color_selector.cpp index 30d31035d9..9ca41e3080 100644 --- a/libs/widgets/kis_spinbox_color_selector.cpp +++ b/libs/widgets/kis_spinbox_color_selector.cpp @@ -1,273 +1,267 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_spinbox_color_selector.h" #include #include #include "kis_double_parse_spin_box.h" #include "kis_int_parse_spin_box.h" #include "kis_signal_compressor.h" #include #ifdef HAVE_OPENEXR #include #endif #include #include #include #include struct KisSpinboxColorSelector::Private { QList labels; QList spinBoxList; QList doubleSpinBoxList; KoColor color; const KoColorSpace *cs {0}; bool chooseAlpha {false}; QFormLayout *layout {0}; }; KisSpinboxColorSelector::KisSpinboxColorSelector(QWidget *parent) : QWidget(parent) , m_d(new Private) { this->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); m_d->layout = new QFormLayout(this); } KisSpinboxColorSelector::~KisSpinboxColorSelector() { } void KisSpinboxColorSelector::slotSetColor(KoColor color) { m_d->color = color; slotSetColorSpace(m_d->color.colorSpace()); updateSpinboxesWithNewValues(); } void KisSpinboxColorSelector::slotSetColorSpace(const KoColorSpace *cs) { if (cs == m_d->cs) { return; } m_d->cs = cs; //remake spinboxes delete m_d->layout; m_d->layout = new QFormLayout(this); Q_FOREACH(QObject *o, m_d->labels) { o->deleteLater(); } Q_FOREACH(QObject *o, m_d->spinBoxList) { o->deleteLater(); } Q_FOREACH(QObject *o, m_d->doubleSpinBoxList) { o->deleteLater(); } - Q_FOREACH(QObject *o, m_d->labels) { - o->deleteLater(); - } - m_d->labels.clear(); m_d->spinBoxList.clear(); m_d->doubleSpinBoxList.clear(); QList channels = KoChannelInfo::displayOrderSorted(m_d->cs->channels()); Q_FOREACH (KoChannelInfo* channel, channels) { QString inputLabel = channel->name(); QLabel *inlb = new QLabel(this); m_d->labels << inlb; inlb->setText(inputLabel); switch (channel->channelValueType()) { case KoChannelInfo::UINT8: { KisIntParseSpinBox *input = new KisIntParseSpinBox(this); input->setMinimum(0); input->setMaximum(0xFF); m_d->spinBoxList.append(input); m_d->layout->addRow(inlb, input); connect(input, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateFromSpinBoxes())); if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) { inlb->setVisible(false); input->setVisible(false); input->blockSignals(true); } } break; case KoChannelInfo::UINT16: { KisIntParseSpinBox *input = new KisIntParseSpinBox(this); input->setMinimum(0); input->setMaximum(0xFFFF); m_d->spinBoxList.append(input); m_d->layout->addRow(inlb,input); connect(input, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateFromSpinBoxes())); if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) { inlb->setVisible(false); input->setVisible(false); input->blockSignals(true); } } break; case KoChannelInfo::UINT32: { KisIntParseSpinBox *input = new KisIntParseSpinBox(this); input->setMinimum(0); input->setMaximum(0xFFFFFFFF); m_d->spinBoxList.append(input); m_d->layout->addRow(inlb,input); connect(input, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateFromSpinBoxes())); if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) { inlb->setVisible(false); input->setVisible(false); input->blockSignals(true); } } break; #ifdef HAVE_OPENEXR case KoChannelInfo::FLOAT16: { KisDoubleParseSpinBox *input = new KisDoubleParseSpinBox(this); input->setMinimum(0); input->setMaximum(KoColorSpaceMathsTraits::max); input->setSingleStep(0.1); m_d->doubleSpinBoxList.append(input); m_d->layout->addRow(inlb,input); connect(input, SIGNAL(valueChanged(double)), this, SLOT(slotUpdateFromSpinBoxes())); if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) { inlb->setVisible(false); input->setVisible(false); input->blockSignals(true); } } break; #endif case KoChannelInfo::FLOAT32: { KisDoubleParseSpinBox *input = new KisDoubleParseSpinBox(this); input->setMinimum(0); input->setMaximum(KoColorSpaceMathsTraits::max); input->setSingleStep(0.1); m_d->doubleSpinBoxList.append(input); m_d->layout->addRow(inlb,input); connect(input, SIGNAL(valueChanged(double)), this, SLOT(slotUpdateFromSpinBoxes())); if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) { inlb->setVisible(false); input->setVisible(false); input->blockSignals(true); } } break; default: Q_ASSERT(false); } } this->setLayout(m_d->layout); } void KisSpinboxColorSelector::createColorFromSpinboxValues() { - KoColor newColor; + KoColor newColor(m_d->cs); int channelcount = m_d->cs->channelCount(); - quint8 *data = new quint8[m_d->cs->pixelSize()]; QVector channelValues(channelcount); channelValues.fill(1.0); QList channels = KoChannelInfo::displayOrderSorted(m_d->cs->channels()); for (int i = 0; i < (int)qAbs(m_d->cs->colorChannelCount()); i++) { int channelposition = KoChannelInfo::displayPositionToChannelIndex(i, m_d->cs->channels()); if (channels.at(i)->channelValueType()==KoChannelInfo::UINT8 && m_d->spinBoxList.at(i)){ int value = m_d->spinBoxList.at(i)->value(); channelValues[channelposition] = KoColorSpaceMaths::scaleToA(value); } else if (channels.at(i)->channelValueType()==KoChannelInfo::UINT16 && m_d->spinBoxList.at(i)){ channelValues[channelposition] = KoColorSpaceMaths::scaleToA(m_d->spinBoxList.at(i)->value()); } else if ((channels.at(i)->channelValueType()==KoChannelInfo::FLOAT16 || channels.at(i)->channelValueType()==KoChannelInfo::FLOAT32 || channels.at(i)->channelValueType()==KoChannelInfo::FLOAT64) && m_d->doubleSpinBoxList.at(i)) { channelValues[channelposition] = m_d->doubleSpinBoxList.at(i)->value(); } } - m_d->cs->fromNormalisedChannelsValue(data, channelValues); - newColor.setColor(data, m_d->cs); + m_d->cs->fromNormalisedChannelsValue(newColor.data(), channelValues); newColor.setOpacity(m_d->color.opacityU8()); m_d->color = newColor; } void KisSpinboxColorSelector::slotUpdateFromSpinBoxes() { createColorFromSpinboxValues(); emit sigNewColor(m_d->color); } void KisSpinboxColorSelector::updateSpinboxesWithNewValues() { int channelcount = m_d->cs->channelCount(); QVector channelValues(channelcount); channelValues.fill(1.0); m_d->cs->normalisedChannelsValue(m_d->color.data(), channelValues); QList channels = KoChannelInfo::displayOrderSorted(m_d->cs->channels()); int i; /*while (QLayoutItem *item = this->layout()->takeAt(0)) { item->widget()->blockSignals(true); }*/ for (i=0; ispinBoxList.size(); i++) { m_d->spinBoxList.at(i)->blockSignals(true); } for (i=0; idoubleSpinBoxList.size(); i++) { m_d->doubleSpinBoxList.at(i)->blockSignals(true); } for (i = 0; i < (int)qAbs(m_d->cs->colorChannelCount()); i++) { int channelposition = KoChannelInfo::displayPositionToChannelIndex(i, m_d->cs->channels()); if (channels.at(i)->channelValueType() == KoChannelInfo::UINT8 && m_d->spinBoxList.at(i)) { int value = KoColorSpaceMaths::scaleToA(channelValues[channelposition]); m_d->spinBoxList.at(i)->setValue(value); } else if (channels.at(i)->channelValueType() == KoChannelInfo::UINT16 && m_d->spinBoxList.at(i)) { m_d->spinBoxList.at(i)->setValue(KoColorSpaceMaths::scaleToA(channelValues[channelposition])); } else if ((channels.at(i)->channelValueType()==KoChannelInfo::FLOAT16 || channels.at(i)->channelValueType()==KoChannelInfo::FLOAT32 || channels.at(i)->channelValueType()==KoChannelInfo::FLOAT64) && m_d->doubleSpinBoxList.at(i)) { m_d->doubleSpinBoxList.at(i)->setValue(channelValues[channelposition]); } } for (i=0; ispinBoxList.size(); i++) { m_d->spinBoxList.at(i)->blockSignals(false); } for (i=0; idoubleSpinBoxList.size(); i++) { m_d->doubleSpinBoxList.at(i)->blockSignals(false); } /*while (QLayoutItem *item = this->layout()->takeAt(0)) { item->widget()->blockSignals(false); }*/ } diff --git a/libs/widgetutils/KisSqueezedComboBox.cpp b/libs/widgetutils/KisSqueezedComboBox.cpp index 6e5d35baec..afd5de755d 100644 --- a/libs/widgetutils/KisSqueezedComboBox.cpp +++ b/libs/widgetutils/KisSqueezedComboBox.cpp @@ -1,179 +1,178 @@ /* ============================================================ * Author: Tom Albers * Date : 2005-01-01 * Description : * * Copyright 2005 by Tom Albers * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option)* any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "KisSqueezedComboBox.h" /** @file KisSqueezedComboBox.cpp */ // Qt includes. #include #include #include #include #include #include KisSqueezedComboBox::KisSqueezedComboBox(QWidget *parent, const char *name) : QComboBox(parent) { setObjectName(name); setMinimumWidth(100); m_timer = new QTimer(this); m_timer->setSingleShot(true); connect(m_timer, SIGNAL(timeout()), SLOT(slotTimeOut())); } KisSqueezedComboBox::~KisSqueezedComboBox() { delete m_timer; } bool KisSqueezedComboBox::contains(const QString& _text) const { if (_text.isEmpty()) return false; for (QMap::const_iterator it = m_originalItems.begin() ; it != m_originalItems.end(); ++it) { if (it.value() == _text) { return true; } } return false; } qint32 KisSqueezedComboBox::findOriginalText(const QString& text) const { for (int i = 0; i < m_originalItems.size(); i++) { if(m_originalItems.value(i) == text) { return i; } } return -1; } QStringList KisSqueezedComboBox::originalTexts() const { return m_originalItems.values(); } void KisSqueezedComboBox::resetOriginalTexts(const QStringList &texts) { if (texts == m_originalItems.values()) return; clear(); m_originalItems.clear(); Q_FOREACH (const QString &item, texts) { addSqueezedItem(item); } } QSize KisSqueezedComboBox::sizeHint() const { ensurePolished(); QFontMetrics fm = fontMetrics(); int maxW = count() ? 18 : 7 * fm.boundingRect(QChar('x')).width() + 18; int maxH = qMax(fm.lineSpacing(), 14) + 2; QStyleOptionComboBox options; options.initFrom(this); - return style()->sizeFromContents(QStyle::CT_ComboBox, &options, - QSize(maxW, maxH), this).expandedTo(QApplication::globalStrut()); + return style()->sizeFromContents(QStyle::CT_ComboBox, &options, QSize(maxW, maxH), this); } void KisSqueezedComboBox::insertSqueezedItem(const QString& newItem, int index, QVariant userData) { m_originalItems[index] = newItem; QComboBox::insertItem(index, squeezeText(newItem, this), userData); } void KisSqueezedComboBox::insertSqueezedItem(const QIcon &icon, const QString &newItem, int index, QVariant userData) { m_originalItems[index] = newItem; QComboBox::insertItem(index, icon, squeezeText(newItem, this), userData); } void KisSqueezedComboBox::addSqueezedItem(const QString& newItem, QVariant userData) { insertSqueezedItem(newItem, count(), userData); } void KisSqueezedComboBox::addSqueezedItem(const QIcon &icon, const QString &newItem, QVariant userData) { insertSqueezedItem(icon, newItem, count(), userData); } void KisSqueezedComboBox::setCurrent(const QString& itemText) { qint32 itemIndex = findOriginalText(itemText); if (itemIndex >= 0) { setCurrentIndex(itemIndex); } } void KisSqueezedComboBox::resizeEvent(QResizeEvent *) { m_timer->start(200); } void KisSqueezedComboBox::slotTimeOut() { for (QMap::iterator it = m_originalItems.begin() ; it != m_originalItems.end(); ++it) { setItemText(it.key(), squeezeText(it.value(), this)); } } QString KisSqueezedComboBox::squeezeText(const QString& original, const QWidget *widget) { // not the complete widgetSize is usable. Need to compensate for that. int widgetSize = widget->width() - 30; QFontMetrics fm(widget->fontMetrics()); // If we can fit the full text, return that. if (fm.boundingRect(original).width() < widgetSize) return(original); // We need to squeeze. QString sqItem = original; // prevent empty return value; widgetSize = widgetSize - fm.boundingRect("...").width(); for (int i = 0 ; i != original.length(); ++i) { if ((int)fm.boundingRect(original.right(i)).width() > widgetSize) { sqItem = QString("..." + original.right(--i)); break; } } return sqItem; } QString KisSqueezedComboBox::currentUnsqueezedText() { int curItem = currentIndex(); return m_originalItems[curItem]; } void KisSqueezedComboBox::removeSqueezedItem(int index) { removeItem(index); m_originalItems.remove(index); } diff --git a/libs/widgetutils/config/kstandardaction.h b/libs/widgetutils/config/kstandardaction.h index 9055e1614b..729bcb9d6c 100644 --- a/libs/widgetutils/config/kstandardaction.h +++ b/libs/widgetutils/config/kstandardaction.h @@ -1,598 +1,597 @@ /* This file is part of the KDE libraries Copyright (C) 1999,2000 Kurt Granroth Copyright (C) 2001,2002 Ellis Whitehead This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KSTANDARDACTION_H #define KSTANDARDACTION_H #include #include #include class QObject; class QStringList; class QWidget; class QAction; class KRecentFilesAction; class KDualAction; class KToggleAction; class KToggleFullScreenAction; /** * Convenience methods to access all standard KDE actions. * * These actions should be used instead of hardcoding menubar and * toolbar items. Using these actions helps your application easily - * conform to the KDE UI Style Guide - * @see http://developer.kde.org/documentation/standards/kde/style/basics/index.html . + * conform to the KDE UI Style Guide. * * All of the documentation for QAction holds for KStandardAction * also. When in doubt on how things work, check the QAction * documentation first. * Please note that calling any of these methods automatically adds the action * to the actionCollection() of the QObject given by the 'parent' parameter. * * Simple Example:\n * * In general, using standard actions should be a drop in replacement * for regular actions. For example, if you previously had: * * \code * QAction *newAct = new QAction(i18n("&New"), KisIconUtils::loadIcon("document-new"), * KStandardShortcut::shortcut(KStandardShortcut::New), this, * SLOT(fileNew()), actionCollection()); * \endcode * * You could drop that and replace it with: * * \code * QAction *newAct = KStandardAction::openNew(this, SLOT(fileNew()), * actionCollection()); * \endcode * * Non-standard Usages\n * * It is possible to use the standard actions in various * non-recommended ways. Say, for instance, you wanted to have a * standard action (with the associated correct text and icon and * accelerator, etc) but you didn't want it to go in the standard * place (this is not recommended, by the way). One way to do this is * to simply not use the XML UI framework and plug it into wherever * you want. If you do want to use the XML UI framework (good!), then * it is still possible. * * Basically, the XML building code matches names in the XML code with * the internal names of the actions. You can find out the internal * names of each of the standard actions by using the name * method like so: KStandardAction::name(KStandardAction::Cut) would return * 'edit_cut'. The XML building code will match 'edit_cut' to the * attribute in the global XML file and place your action there. * * However, you can change the internal name. In this example, just * do something like: * * \code * (void)KStandardAction::cut(this, SLOT(editCut()), actionCollection(), "my_cut"); * \endcode * * Now, in your local XML resource file (e.g., yourappui.rc), simply * put 'my_cut' where you want it to go. * * Another non-standard usage concerns getting a pointer to an * existing action if, say, you want to enable or disable the action. * You could do it the recommended way and just grab a pointer when * you instantiate it as in the 'openNew' example above... or you * could do it the hard way: * * \code * QAction *cut = actionCollection()->action(KStandardAction::name(KStandardAction::Cut)); * \endcode * * Another non-standard usage concerns instantiating the action in the * first place. Usually, you would use the member functions as * shown above (e.g., KStandardAction::cut(this, SLOT, parent)). You * may, however, do this using the enums provided. This author can't * think of a reason why you would want to, but, hey, if you do, * here's how: * * \code * (void)KStandardAction::action(KStandardAction::New, this, SLOT(fileNew()), actionCollection()); * (void)KStandardAction::action(KStandardAction::Cut, this, SLOT(editCut()), actionCollection()); * \endcode * * @author Kurt Granroth */ namespace KStandardAction { /** * The standard menubar and toolbar actions. */ enum StandardAction { ActionNone, // File Menu New, Open, OpenRecent, Save, SaveAs, Revert, Close, Print, PrintPreview, Mail, Quit, // Edit Menu Undo, Redo, Cut, Copy, Paste, SelectAll, Deselect, Find, FindNext, FindPrev, Replace, // View Menu ActualSize, FitToPage, FitToWidth, FitToHeight, ZoomIn, ZoomOut, Zoom, Redisplay, // Go Menu Up, Back, Forward, Home /*Home page*/, Prior, Next, Goto, GotoPage, GotoLine, FirstPage, LastPage, DocumentBack, DocumentForward, // Bookmarks Menu AddBookmark, EditBookmarks, // Tools Menu Spelling, // Settings Menu ShowMenubar, ShowToolbar, ShowStatusbar, SaveOptions, KeyBindings, Preferences, ConfigureToolbars, // Help Menu Help, HelpContents, WhatsThis, ReportBug, AboutApp, AboutKDE, TipofDay, // Other standard actions ConfigureNotifications, FullScreen, Clear, PasteText, SwitchApplicationLanguage }; /** * Creates an action corresponding to one of the * KStandardAction::StandardAction actions, which is connected to the given * object and @p slot, and is owned by @p parent. * * The signal that is connected to @p slot is triggered(bool), except for the case of * OpenRecent standard action, which uses the urlSelected(const QUrl &) signal of * KRecentFilesAction. * * @param id The StandardAction identifier to create a QAction for. * @param recvr The QObject to receive the signal, or 0 if no notification * is needed. * @param slot The slot to connect the signal to (remember to use the SLOT() macro). * @param parent The QObject that should own the created QAction, or 0 if no parent will * own the QAction returned (ensure you delete it manually in this case). */ KRITAWIDGETUTILS_EXPORT QAction *create(StandardAction id, const QObject *recvr, const char *slot, QObject *parent); /** * This will return the internal name of a given standard action. */ KRITAWIDGETUTILS_EXPORT const char *name(StandardAction id); /// @deprecated use name() #ifndef KDE_NO_DEPRECATED inline KRITAWIDGETUTILS_DEPRECATED const char *stdName(StandardAction act_enum) { return name(act_enum); } #endif /** * Returns a list of all standard names. Used by KAccelManager * to give those higher weight. */ KRITAWIDGETUTILS_EXPORT QStringList stdNames(); /** * Returns a list of all actionIds. * * @since 4.2 */ KRITAWIDGETUTILS_EXPORT QList actionIds(); /** * Returns the standardshortcut associated with @a actionId. * * @param id The actionId whose associated shortcut is wanted. * * @since 4.2 */ KRITAWIDGETUTILS_EXPORT KStandardShortcut::StandardShortcut shortcutForActionId(StandardAction id); /** * Create a new document or window. */ KRITAWIDGETUTILS_EXPORT QAction *openNew(const QObject *recvr, const char *slot, QObject *parent); /** * Open an existing file. */ KRITAWIDGETUTILS_EXPORT QAction *open(const QObject *recvr, const char *slot, QObject *parent); /** * Open a recently used document. The signature of the slot being called * is of the form slotURLSelected( const QUrl & ). * @param recvr object to receive slot * @param slot The SLOT to invoke when a URL is selected. The slot's * signature is slotURLSelected( const QUrl & ). * @param parent parent widget */ KRITAWIDGETUTILS_EXPORT KRecentFilesAction *openRecent(const QObject *recvr, const char *slot, QObject *parent); /** * Save the current document. */ KRITAWIDGETUTILS_EXPORT QAction *save(const QObject *recvr, const char *slot, QObject *parent); /** * Save the current document under a different name. */ KRITAWIDGETUTILS_EXPORT QAction *saveAs(const QObject *recvr, const char *slot, QObject *parent); /** * Revert the current document to the last saved version * (essentially will undo all changes). */ KRITAWIDGETUTILS_EXPORT QAction *revert(const QObject *recvr, const char *slot, QObject *parent); /** * Close the current document. */ KRITAWIDGETUTILS_EXPORT QAction *close(const QObject *recvr, const char *slot, QObject *parent); /** * Print the current document. */ KRITAWIDGETUTILS_EXPORT QAction *print(const QObject *recvr, const char *slot, QObject *parent); /** * Show a print preview of the current document. */ KRITAWIDGETUTILS_EXPORT QAction *printPreview(const QObject *recvr, const char *slot, QObject *parent); /** * Mail this document. */ KRITAWIDGETUTILS_EXPORT QAction *mail(const QObject *recvr, const char *slot, QObject *parent); /** * Quit the program. * * Note that you probably want to connect this action to either QWidget::close() * or QApplication::closeAllWindows(), but not QApplication::quit(), so that * KMainWindow::queryClose() is called on any open window (to warn the user * about unsaved changes for example). */ KRITAWIDGETUTILS_EXPORT QAction *quit(const QObject *recvr, const char *slot, QObject *parent); /** * Undo the last operation. */ KRITAWIDGETUTILS_EXPORT QAction *undo(const QObject *recvr, const char *slot, QObject *parent); /** * Redo the last operation. */ KRITAWIDGETUTILS_EXPORT QAction *redo(const QObject *recvr, const char *slot, QObject *parent); /** * Cut selected area and store it in the clipboard. * Calls cut() on the widget with the current focus. */ KRITAWIDGETUTILS_EXPORT QAction *cut(QObject *parent); /** * Copy selected area and store it in the clipboard. * Calls copy() on the widget with the current focus. */ KRITAWIDGETUTILS_EXPORT QAction *copy(QObject *parent); /** * Paste the contents of clipboard at the current mouse or cursor * Calls paste() on the widget with the current focus. */ KRITAWIDGETUTILS_EXPORT QAction *paste(QObject *parent); /** * Clear selected area. Calls clear() on the widget with the current focus. * Note that for some widgets, this may not provide the intended behavior. For * example if you make use of the code above and a K3ListView has the focus, clear() * will clear all of the items in the list. If this is not the intened behavior * and you want to make use of this slot, you can subclass K3ListView and reimplement * this slot. For example the following code would implement a K3ListView without this * behavior: * * \code * class MyListView : public K3ListView { * Q_OBJECT * public: * MyListView( QWidget * parent = 0, const char * name = 0, WFlags f = 0 ) : K3ListView( parent, name, f ) {} * virtual ~MyListView() {} * public Q_SLOTS: * virtual void clear() {} * }; * \endcode */ KRITAWIDGETUTILS_EXPORT QAction *clear(QObject *parent); /** * Calls selectAll() on the widget with the current focus. */ KRITAWIDGETUTILS_EXPORT QAction *selectAll(QObject *parent); /** * Cut selected area and store it in the clipboard. */ KRITAWIDGETUTILS_EXPORT QAction *cut(const QObject *recvr, const char *slot, QObject *parent); /** * Copy the selected area into the clipboard. */ KRITAWIDGETUTILS_EXPORT QAction *copy(const QObject *recvr, const char *slot, QObject *parent); /** * Paste the contents of clipboard at the current mouse or cursor * position. */ KRITAWIDGETUTILS_EXPORT QAction *paste(const QObject *recvr, const char *slot, QObject *parent); /** * Paste the contents of clipboard at the current mouse or cursor * position. Provide a button on the toolbar with the clipboard history * menu if Klipper is running. */ KRITAWIDGETUTILS_EXPORT QAction *pasteText(const QObject *recvr, const char *slot, QObject *parent); /** * Clear the content of the focus widget */ KRITAWIDGETUTILS_EXPORT QAction *clear(const QObject *recvr, const char *slot, QObject *parent); /** * Select all elements in the current document. */ KRITAWIDGETUTILS_EXPORT QAction *selectAll(const QObject *recvr, const char *slot, QObject *parent); /** * Deselect any selected elements in the current document. */ KRITAWIDGETUTILS_EXPORT QAction *deselect(const QObject *recvr, const char *slot, QObject *parent); /** * Initiate a 'find' request in the current document. */ KRITAWIDGETUTILS_EXPORT QAction *find(const QObject *recvr, const char *slot, QObject *parent); /** * Find the next instance of a stored 'find'. */ KRITAWIDGETUTILS_EXPORT QAction *findNext(const QObject *recvr, const char *slot, QObject *parent); /** * Find a previous instance of a stored 'find'. */ KRITAWIDGETUTILS_EXPORT QAction *findPrev(const QObject *recvr, const char *slot, QObject *parent); /** * Find and replace matches. */ KRITAWIDGETUTILS_EXPORT QAction *replace(const QObject *recvr, const char *slot, QObject *parent); /** * View the document at its actual size. */ KRITAWIDGETUTILS_EXPORT QAction *actualSize(const QObject *recvr, const char *slot, QObject *parent); /** * Fit the document view to the size of the current window. */ KRITAWIDGETUTILS_EXPORT QAction *fitToPage(const QObject *recvr, const char *slot, QObject *parent); /** * Fit the document view to the width of the current window. */ KRITAWIDGETUTILS_EXPORT QAction *fitToWidth(const QObject *recvr, const char *slot, QObject *parent); /** * Fit the document view to the height of the current window. */ KRITAWIDGETUTILS_EXPORT QAction *fitToHeight(const QObject *recvr, const char *slot, QObject *parent); /** * Zoom in. */ KRITAWIDGETUTILS_EXPORT QAction *zoomIn(const QObject *recvr, const char *slot, QObject *parent); /** * Zoom out. */ KRITAWIDGETUTILS_EXPORT QAction *zoomOut(const QObject *recvr, const char *slot, QObject *parent); /** * Popup a zoom dialog. */ KRITAWIDGETUTILS_EXPORT QAction *zoom(const QObject *recvr, const char *slot, QObject *parent); /** * Redisplay or redraw the document. */ KRITAWIDGETUTILS_EXPORT QAction *redisplay(const QObject *recvr, const char *slot, QObject *parent); /** * Move up (web style menu). */ KRITAWIDGETUTILS_EXPORT QAction *up(const QObject *recvr, const char *slot, QObject *parent); /** * Move back (web style menu). */ KRITAWIDGETUTILS_EXPORT QAction *back(const QObject *recvr, const char *slot, QObject *parent); /** * Move forward (web style menu). */ KRITAWIDGETUTILS_EXPORT QAction *forward(const QObject *recvr, const char *slot, QObject *parent); /** * Go to the "Home" position or document. */ KRITAWIDGETUTILS_EXPORT QAction *home(const QObject *recvr, const char *slot, QObject *parent); /** * Scroll up one page. */ KRITAWIDGETUTILS_EXPORT QAction *prior(const QObject *recvr, const char *slot, QObject *parent); /** * Scroll down one page. */ KRITAWIDGETUTILS_EXPORT QAction *next(const QObject *recvr, const char *slot, QObject *parent); /** * Go to somewhere in general. */ KRITAWIDGETUTILS_EXPORT QAction *goTo(const QObject *recvr, const char *slot, QObject *parent); /** * Go to a specific page (dialog). */ KRITAWIDGETUTILS_EXPORT QAction *gotoPage(const QObject *recvr, const char *slot, QObject *parent); /** * Go to a specific line (dialog). */ KRITAWIDGETUTILS_EXPORT QAction *gotoLine(const QObject *recvr, const char *slot, QObject *parent); /** * Jump to the first page. */ KRITAWIDGETUTILS_EXPORT QAction *firstPage(const QObject *recvr, const char *slot, QObject *parent); /** * Jump to the last page. */ KRITAWIDGETUTILS_EXPORT QAction *lastPage(const QObject *recvr, const char *slot, QObject *parent); /** * Move back (document style menu). */ KRITAWIDGETUTILS_EXPORT QAction *documentBack(const QObject *recvr, const char *slot, QObject *parent); /** * Move forward (document style menu). */ KRITAWIDGETUTILS_EXPORT QAction *documentForward(const QObject *recvr, const char *slot, QObject *parent); /** * Add the current page to the bookmarks tree. */ KRITAWIDGETUTILS_EXPORT QAction *addBookmark(const QObject *recvr, const char *slot, QObject *parent); /** * Edit the application bookmarks. */ KRITAWIDGETUTILS_EXPORT QAction *editBookmarks(const QObject *recvr, const char *slot, QObject *parent); /** * Pop up the spell checker. */ KRITAWIDGETUTILS_EXPORT QAction *spelling(const QObject *recvr, const char *slot, QObject *parent); /** * Show/Hide the menubar. */ KRITAWIDGETUTILS_EXPORT KToggleAction *showMenubar(const QObject *recvr, const char *slot, QObject *parent); /** * Show/Hide the statusbar. */ KRITAWIDGETUTILS_EXPORT KToggleAction *showStatusbar(const QObject *recvr, const char *slot, QObject *parent); /** * Switch to/from full screen mode */ KRITAWIDGETUTILS_EXPORT KToggleFullScreenAction *fullScreen(const QObject *recvr, const char *slot, QWidget *window, QObject *parent); /** * Display the save options dialog. */ KRITAWIDGETUTILS_EXPORT QAction *saveOptions(const QObject *recvr, const char *slot, QObject *parent); /** * Display the configure key bindings dialog. * * Note that you might be able to use the pre-built KXMLGUIFactory's function: * KStandardAction::keyBindings(guiFactory(), SLOT(configureShortcuts()), actionCollection()); */ KRITAWIDGETUTILS_EXPORT QAction *keyBindings(const QObject *recvr, const char *slot, QObject *parent); /** * Display the preferences/options dialog. */ KRITAWIDGETUTILS_EXPORT QAction *preferences(const QObject *recvr, const char *slot, QObject *parent); /** * The Customize Toolbar dialog. */ KRITAWIDGETUTILS_EXPORT QAction *configureToolbars(const QObject *recvr, const char *slot, QObject *parent); /** * The Configure Notifications dialog. */ KRITAWIDGETUTILS_EXPORT QAction *configureNotifications(const QObject *recvr, const char *slot, QObject *parent); /** * Display the help. */ KRITAWIDGETUTILS_EXPORT QAction *help(const QObject *recvr, const char *slot, QObject *parent); /** * Display the help contents. */ KRITAWIDGETUTILS_EXPORT QAction *helpContents(const QObject *recvr, const char *slot, QObject *parent); /** * Trigger the What's This cursor. */ KRITAWIDGETUTILS_EXPORT QAction *whatsThis(const QObject *recvr, const char *slot, QObject *parent); /** * Display "Tip of the Day" */ KRITAWIDGETUTILS_EXPORT QAction *tipOfDay(const QObject *recvr, const char *slot, QObject *parent); /** * Open up the Report Bug dialog. */ KRITAWIDGETUTILS_EXPORT QAction *reportBug(const QObject *recvr, const char *slot, QObject *parent); /** * Display the application's About box. */ KRITAWIDGETUTILS_EXPORT QAction *aboutApp(const QObject *recvr, const char *slot, QObject *parent); /** * Display the About KDE dialog. */ KRITAWIDGETUTILS_EXPORT QAction *aboutKDE(const QObject *recvr, const char *slot, QObject *parent); } #endif // KSTDACTION_H diff --git a/libs/widgetutils/kis_double_parse_spin_box.cpp b/libs/widgetutils/kis_double_parse_spin_box.cpp index 50005053dd..47c4ead442 100644 --- a/libs/widgetutils/kis_double_parse_spin_box.cpp +++ b/libs/widgetutils/kis_double_parse_spin_box.cpp @@ -1,236 +1,237 @@ /* * Copyright (c) 2016 Laurent Valentin Jospin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * 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_double_parse_spin_box.h" #include "kis_num_parser.h" #include #include #include #include #include #include // for qIsNaN KisDoubleParseSpinBox::KisDoubleParseSpinBox(QWidget *parent) : QDoubleSpinBox(parent), boolLastValid(true), lastExprParsed(QStringLiteral("0.0")) { setAlignment(Qt::AlignRight); connect(this, SIGNAL(noMoreParsingError()), this, SLOT(clearErrorStyle())); //hack to let the clearError be called, even if the value changed method is the one from QDoubleSpinBox. connect(this, SIGNAL(valueChanged(double)), this, SLOT(clearError())); connect(this, SIGNAL(errorWhileParsing(QString)), this, SLOT(setErrorStyle())); oldValue = value(); warningIcon = new QLabel(this); if (QFile(":/./16_light_warning.svg").exists()) { warningIcon->setPixmap(QIcon(":/./16_light_warning.svg").pixmap(16, 16)); } else { warningIcon->setText("!"); } warningIcon->setStyleSheet("background:transparent;"); warningIcon->move(1, 1); warningIcon->setVisible(false); isOldPaletteSaved = false; areOldMarginsSaved = false; } KisDoubleParseSpinBox::~KisDoubleParseSpinBox() { } double KisDoubleParseSpinBox::valueFromText(const QString & text) const { lastExprParsed = text; bool ok; double ret; if ( (suffix().isEmpty() || !text.endsWith(suffix())) && (prefix().isEmpty() || !text.startsWith(prefix())) ) { ret = KisNumericParser::parseSimpleMathExpr(text, &ok); } else { QString expr = text; if (text.endsWith(suffix())) { expr.remove(text.size()-suffix().size(), suffix().size()); } if(text.startsWith(prefix())){ expr.remove(0, prefix().size()); } lastExprParsed = expr; ret = KisNumericParser::parseSimpleMathExpr(expr, &ok); } if(qIsNaN(ret) || qIsInf(ret)){ ok = false; } if (!ok) { if (boolLastValid) { oldValue = value(); } boolLastValid = false; ret = oldValue; //in case of error set to minimum. } else { if (!boolLastValid) { oldValue = ret; } boolLastValid = true; } return ret; } QString KisDoubleParseSpinBox::textFromValue(double val) const { if (!boolLastValid) { emit errorWhileParsing(lastExprParsed); return lastExprParsed; } emit noMoreParsingError(); return QDoubleSpinBox::textFromValue(val); } QString KisDoubleParseSpinBox::veryCleanText() const { return cleanText(); } QValidator::State KisDoubleParseSpinBox::validate ( QString & input, int & pos ) const { Q_UNUSED(input); Q_UNUSED(pos); return QValidator::Acceptable; } void KisDoubleParseSpinBox::stepBy(int steps) { boolLastValid = true; //reset to valid state so we can use the up and down buttons. emit noMoreParsingError(); QDoubleSpinBox::stepBy(steps); } void KisDoubleParseSpinBox::setValue(double value) { - if(value == oldValue && hasFocus()){ //avoid to reset the button when it set the value of something that will recall this slot. + // Avoid to reset the button when it set the val of something that will recall this slot. + if(hasFocus() && QString::number( value, 'f', this->decimals()) == QString::number( oldValue, 'f', this->decimals())){ return; } QDoubleSpinBox::setValue(value); if (!hasFocus()) { clearError(); } } void KisDoubleParseSpinBox::setErrorStyle() { if (!boolLastValid) { //setStyleSheet(_oldStyleSheet + "Background: red; color: white; padding-left: 18px;"); if (!isOldPaletteSaved) { oldPalette = palette(); } isOldPaletteSaved = true; QPalette nP = oldPalette; nP.setColor(QPalette::Background, Qt::red); nP.setColor(QPalette::Base, Qt::red); nP.setColor(QPalette::Text, Qt::white); setPalette(nP); if (!areOldMarginsSaved) { oldMargins = lineEdit()->textMargins(); } areOldMarginsSaved = true; if (width() - height() >= 3*height()) { //if we have twice as much place as needed by the warning icon then display it. QMargins newMargins = oldMargins; newMargins.setLeft( newMargins.left() + height() - 4 ); lineEdit()->setTextMargins(newMargins); int h = warningIcon->height(); int hp = height()-2; if (h != hp) { warningIcon->resize(hp, hp); if (QFile(":/./16_light_warning.svg").exists()) { warningIcon->setPixmap(QIcon(":/./16_light_warning.svg").pixmap(hp-7, hp-7)); } } warningIcon->move(oldMargins.left()+4, 1); warningIcon->setVisible(true); } } } void KisDoubleParseSpinBox::clearErrorStyle() { if (boolLastValid) { warningIcon->setVisible(false); //setStyleSheet(QString()); setPalette(oldPalette); isOldPaletteSaved = false; lineEdit()->setTextMargins(oldMargins); areOldMarginsSaved = false; } } void KisDoubleParseSpinBox::clearError() { boolLastValid = true; emit noMoreParsingError(); oldValue = value(); clearErrorStyle(); } diff --git a/libs/widgetutils/xmlgui/KisShortcutsDialog.cpp b/libs/widgetutils/xmlgui/KisShortcutsDialog.cpp index 8e52aa2fc6..55c9d2a113 100644 --- a/libs/widgetutils/xmlgui/KisShortcutsDialog.cpp +++ b/libs/widgetutils/xmlgui/KisShortcutsDialog.cpp @@ -1,150 +1,148 @@ /* This file is part of the KDE libraries Copyright (C) 1998 Mark Donohoe Copyright (C) 1997 Nicolas Hadacek Copyright (C) 1998 Mark Donohoe Copyright (C) 1998 Matthias Ettrich Copyright (C) 1999 Espen Sand Copyright (C) 2001 Ellis Whitehead Copyright (C) 2006 Hamish Rodda Copyright (C) 2007 Roberto Raggi Copyright (C) 2007 Andreas Hartmetz Copyright (C) 2008 Michael Jansen Copyright (C) 2008 Alexander Dymo Copyright (C) 2009 Chani Armitage 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 "KisShortcutsDialog.h" #include "KisShortcutsDialog_p.h" #include "kshortcutschemeshelper_p.h" #include "kshortcutschemeseditor.h" #include #include #include #include #include #include #include #include #include "kxmlguiclient.h" #include "kxmlguifactory.h" #include "kactioncollection.h" KisShortcutsDialog::KisShortcutsDialog(KisShortcutsEditor::ActionTypes types, KisShortcutsEditor::LetterShortcuts allowLetterShortcuts, QWidget *parent) : QWidget(parent) , d(new KisShortcutsDialogPrivate(this)) { d->m_shortcutsEditor = new KisShortcutsEditor(this, types, allowLetterShortcuts); - - /* Construct & Connect UI */ QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(d->m_shortcutsEditor); QHBoxLayout *bottomLayout = new QHBoxLayout; d->m_schemeEditor = new KShortcutSchemesEditor(this); connect(d->m_schemeEditor, SIGNAL(shortcutsSchemeChanged(QString)), this, SLOT(changeShortcutScheme(QString))); bottomLayout->addLayout(d->m_schemeEditor); QPushButton *printButton = new QPushButton; KGuiItem::assign(printButton, KStandardGuiItem::print()); QDialogButtonBox *buttonBox = new QDialogButtonBox(this); buttonBox->addButton(printButton, QDialogButtonBox::ActionRole); bottomLayout->addWidget(buttonBox); mainLayout->addLayout(bottomLayout); connect(printButton, SIGNAL(clicked()), d->m_shortcutsEditor, SLOT(printShortcuts())); KConfigGroup group(KSharedConfig::openConfig(), "KisShortcutsDialog Settings"); resize(group.readEntry("Dialog Size", sizeHint())); } KisShortcutsDialog::~KisShortcutsDialog() { KConfigGroup group(KSharedConfig::openConfig(), "KisShortcutsDialog Settings"); group.writeEntry("Dialog Size", size()); delete d; } void KisShortcutsDialog::addCollection(KActionCollection *collection, const QString &title) { d->m_shortcutsEditor->addCollection(collection, title); d->m_collections.insert(title, collection); } void KisShortcutsDialog::save() { d->save(); } QList KisShortcutsDialog::actionCollections() const { return d->m_collections.values(); } QSize KisShortcutsDialog::sizeHint() const { return QSize(600, 480); } void KisShortcutsDialog::allDefault() { d->m_shortcutsEditor->allDefault(); } void KisShortcutsDialog::undo() { d->undo(); } void KisShortcutsDialog::importConfiguration(const QString &path) { auto config = KSharedConfig::openConfig(path); d->m_shortcutsEditor->importConfiguration(config.data(), true); } void KisShortcutsDialog::exportConfiguration(const QString &path) const { auto config = KSharedConfig::openConfig(path); d->m_shortcutsEditor->exportConfiguration(config.data()); } void KisShortcutsDialog::saveCustomShortcuts(const QString &path) const { auto cg = KSharedConfig::openConfig(path)->group(QStringLiteral("Shortcuts")); d->m_shortcutsEditor->saveShortcuts(&cg); d->m_shortcutsEditor->commit(); } void KisShortcutsDialog::loadCustomShortcuts(const QString &path) { auto config = KSharedConfig::openConfig(path); d->m_shortcutsEditor->importConfiguration(config.data(), false); } #include "moc_KisShortcutsDialog.cpp" diff --git a/libs/widgetutils/xmlgui/KisShortcutsDialog_p.h b/libs/widgetutils/xmlgui/KisShortcutsDialog_p.h index eb1b9c2a66..de1105abd0 100644 --- a/libs/widgetutils/xmlgui/KisShortcutsDialog_p.h +++ b/libs/widgetutils/xmlgui/KisShortcutsDialog_p.h @@ -1,236 +1,235 @@ /* This file is part of the KDE libraries Copyright (C) 2006,2007 Andreas Hartmetz (ahartmetz@gmail.com) Copyright (C) 2008 Michael Jansen Copyright (C) 2008 Alexander Dymo 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 KISSHORTCUTSDIALOG_P_H #define KISSHORTCUTSDIALOG_P_H #include "KisShortcutsEditor.h" #include "kkeysequencewidget.h" #include "KisShortcutsDialog.h" #include #include #include #include #include #include #include #include #include class QLabel; class QTreeWidget; class QTreeWidgetItem; class QRadioButton; class QAction; class KActionCollection; class QPushButton; class QComboBox; class KisShortcutsDialog; class KShortcutSchemesEditor; class QAction; enum ColumnDesignation { Name = 0, LocalPrimary, LocalAlternate, Id }; // XXX: Hmm enum MyRoles { ShortcutRole = Qt::UserRole, DefaultShortcutRole, ObjectRole }; /** * Type used for QTreeWidgetItems * * @internal */ enum ItemTypes { NonActionItem = 0, ActionItem = 1 }; // Return the first item of the list, if it exists QKeySequence primarySequence(const QList &sequences); // Return the second item of the list, if it exists QKeySequence alternateSequence(const QList &sequences); class KisShortcutsDialog::KisShortcutsDialogPrivate { public: KisShortcutsDialogPrivate(KisShortcutsDialog *q); void changeShortcutScheme(const QString &scheme); void undo(); void save(); QHash m_collections; KisShortcutsDialog *q; - KisShortcutsEditor *m_shortcutsEditor; - + KisShortcutsEditor *m_shortcutsEditor {0}; KShortcutSchemesEditor *m_schemeEditor{0}; }; /** * Mixes the KShortcutWidget into the treeview used by KisShortcutsEditor. When * selecting an shortcut it changes the display from "CTRL-W" to the Widget. * * @bug That delegate uses KExtendableItemDelegate. That means a cell can be * expanded. When selected a cell is replaced by a KisShortcutsEditor. When * painting the widget KExtendableItemDelegate reparents the widget to the * viewport of the itemview it belongs to. The widget is destroyed when the user * selects another shortcut or explicitly issues a contractItem event. But when * the user clears the model the delegate misses that event and doesn't delete * the KShortcutseditor. And remains as a visible artifact in your treeview. * Additionally when closing your application you get an assertion failure from * KExtendableItemDelegate. * * @internal */ class KisShortcutsEditorDelegate : public KExtendableItemDelegate { Q_OBJECT public: KisShortcutsEditorDelegate(QTreeWidget *parent, bool allowLetterShortcuts); //reimplemented to have some extra height QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; /** * Set a list of action collections to check against for conflicting * shortcuts. * * @see KKeySequenceWidget::setCheckActionCollections */ void setCheckActionCollections(const QList checkActionCollections); bool eventFilter(QObject *, QEvent *) override; private: mutable QPersistentModelIndex m_editingIndex; bool m_allowLetterShortcuts; QWidget *m_editor; //! List of actionCollections to check for conflicts. QList m_checkActionCollections; Q_SIGNALS: void shortcutChanged(QVariant, const QModelIndex &); public Q_SLOTS: void hiddenBySearchLine(QTreeWidgetItem *, bool); private Q_SLOTS: void itemActivated(QModelIndex index); /** * When the user collapses a hole subtree of shortcuts then remove eventually * extended items. Else we get that artifact bug. See above. */ void itemCollapsed(QModelIndex index); /** * If the user allowed stealing a shortcut we want to be able to undo * that. */ void stealShortcut(const QKeySequence &seq, QAction *action); void keySequenceChanged(const QKeySequence &); }; /** * Edit a shortcut. This widget is displayed when a user clicks on a shortcut * from the list. It contains radio buttons choosing between default and custom * shortcuts, and a button to configure the custom shortcut. * * @see KisShortcutsEditorDeligate::itemActivated * @see KisShortcutWidget.cpp * * @internal */ class ShortcutEditWidget : public QWidget { Q_OBJECT public: ShortcutEditWidget(QWidget *viewport, const QKeySequence &defaultSeq, const QKeySequence &activeSeq, bool allowLetterShortcuts); //! @see KKeySequenceWidget::setCheckActionCollections() void setCheckActionCollections(const QList checkActionCollections); //@{ //! @see KKeySequenceWidget::checkAgainstStandardShortcuts() KKeySequenceWidget::ShortcutTypes checkForConflictsAgainst() const; void setCheckForConflictsAgainst(KKeySequenceWidget::ShortcutTypes); //@} //@{ //! @see KKeySequenceWidget::checkAgainstStandardShortcuts() bool multiKeyShortcutsAllowed() const; void setMultiKeyShortcutsAllowed(bool); //@} //! @see KKeySequenceWidget::setComponentName void setComponentName(const QString componentName); void setAction(QObject *action); void paintEvent(QPaintEvent *pe) override; Q_SIGNALS: //! Emitted when the key sequence is changed. void keySequenceChanged(const QKeySequence &); //! @see KKeySequenceWidget::stealShortcut() void stealShortcut(const QKeySequence &seq, QAction *action); public Q_SLOTS: //! Set the displayed sequences void setKeySequence(const QKeySequence &activeSeq); private Q_SLOTS: void defaultToggled(bool); void setCustom(const QKeySequence &); private: QLabel *m_defaultLabel; QKeySequence m_defaultKeySequence; QRadioButton *m_defaultRadio; QRadioButton *m_customRadio; KKeySequenceWidget *m_customEditor; bool m_isUpdating; QObject *m_action; }; #endif /* KISSHORTCUTSDIALOG_P_H */ diff --git a/libs/widgetutils/xmlgui/kaboutkdedialog_p.cpp b/libs/widgetutils/xmlgui/kaboutkdedialog_p.cpp index cdc1a27ac6..316f0da8a7 100644 --- a/libs/widgetutils/xmlgui/kaboutkdedialog_p.cpp +++ b/libs/widgetutils/xmlgui/kaboutkdedialog_p.cpp @@ -1,159 +1,159 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Urs Wolfer Parts of this class have been take from the KAboutKDE class, which was Copyright (C) 2000 Espen Sand This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kaboutkdedialog_p.h" #include #include #include #include #include #include #include #include #include namespace KDEPrivate { KAboutKdeDialog::KAboutKdeDialog(QWidget *parent) : QDialog(parent), d(0) { setWindowTitle(i18n("About KDE")); KTitleWidget *titleWidget = new KTitleWidget(this); titleWidget->setText(i18n("KDE - Be Free!")); titleWidget->setPixmap(KisIconUtils::loadIcon(QStringLiteral("kde")).pixmap(48), KTitleWidget::ImageLeft); QLabel *about = new QLabel; about->setMargin(10); about->setAlignment(Qt::AlignTop); about->setWordWrap(true); about->setOpenExternalLinks(true); about->setTextInteractionFlags(Qt::TextBrowserInteraction); about->setText(i18n("" "KDE is a world-wide network of software engineers, artists, writers, translators and facilitators " "who are committed to Free Software development. " "This community has created hundreds of Free Software applications as part of the KDE " "frameworks, workspaces and applications.

    " "KDE is a cooperative enterprise in which no single entity controls the " "efforts or products of KDE to the exclusion of others. Everyone is welcome to join and " "contribute to KDE, including you.

    " "Visit %2 for " "more information about the KDE community and the software we produce.", - QStringLiteral("http://www.gnu.org/philosophy/free-sw.html"), - QStringLiteral("http://www.kde.org/"))); + QStringLiteral("https://www.gnu.org/philosophy/free-sw.html"), + QStringLiteral("https://www.kde.org/"))); QLabel *report = new QLabel; report->setMargin(10); report->setAlignment(Qt::AlignTop); report->setWordWrap(true); report->setOpenExternalLinks(true); report->setTextInteractionFlags(Qt::TextBrowserInteraction); report->setText(i18n("" "Software can always be improved, and the KDE team is ready to " "do so. However, you - the user - must tell us when " "something does not work as expected or could be done better.

    " "KDE has a bug tracking system. Visit " "%1 or " "use the \"Report Bug...\" dialog from the \"Help\" menu to report bugs.

    " "If you have a suggestion for improvement then you are welcome to use " "the bug tracking system to register your wish. Make sure you use the " "severity called \"Wishlist\".", QStringLiteral("https://bugs.kde.org/"))); QLabel *join = new QLabel; join->setMargin(10); join->setAlignment(Qt::AlignTop); join->setWordWrap(true); join->setOpenExternalLinks(true); join->setTextInteractionFlags(Qt::TextBrowserInteraction); join->setText(i18n("" "You do not have to be a software developer to be a member of the " "KDE team. You can join the national teams that translate " "program interfaces. You can provide graphics, themes, sounds, and " "improved documentation. You decide!" "

    " "Visit " "%1 " "for information on some projects in which you can participate." "

    " "If you need more information or documentation, then a visit to " "%2 " "will provide you with what you need.", - QStringLiteral("http://www.kde.org/community/getinvolved/"), - QStringLiteral("http://techbase.kde.org/"))); + QStringLiteral("https://community.kde.org/Get_Involved"), + QStringLiteral("https://techbase.kde.org/"))); QLabel *support = new QLabel; support->setMargin(10); support->setAlignment(Qt::AlignTop); support->setWordWrap(true); support->setOpenExternalLinks(true); support->setTextInteractionFlags(Qt::TextBrowserInteraction); support->setText(i18n("" "KDE software is and will always be available free of charge, however creating it is not free.

    " "To support development the KDE community has formed the KDE e.V., a non-profit organization " "legally founded in Germany. KDE e.V. represents the KDE community in legal and financial matters. " "See %1" " for information on KDE e.V.

    " "KDE benefits from many kinds of contributions, including financial. " "We use the funds to reimburse members and others for expenses " "they incur when contributing. Further funds are used for legal " "support and organizing conferences and meetings.

    " "We would like to encourage you to support our efforts with a " "financial donation, using one of the ways described at " "%2." "

    Thank you very much in advance for your support.", - QStringLiteral("http://ev.kde.org/"), - QStringLiteral("http://www.kde.org/community/donations/")) + QLatin1String("

    ")); // FIXME: ugly
    at the end... + QStringLiteral("https://ev.kde.org/"), + QStringLiteral("https://www.kde.org/community/donations/")) + QLatin1String("

    ")); // FIXME: ugly
    at the end... QTabWidget *tabWidget = new QTabWidget; tabWidget->setUsesScrollButtons(false); tabWidget->addTab(about, i18nc("About KDE", "&About")); tabWidget->addTab(report, i18n("&Report Bugs or Wishes")); tabWidget->addTab(join, i18n("&Join KDE")); tabWidget->addTab(support, i18n("&Support KDE")); QLabel *image = new QLabel; image->setPixmap(QStringLiteral(":/kxmlgui5/aboutkde.png")); QHBoxLayout *midLayout = new QHBoxLayout; midLayout->addWidget(image); midLayout->addWidget(tabWidget); QDialogButtonBox *buttonBox = new QDialogButtonBox; buttonBox->setStandardButtons(QDialogButtonBox::Close); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(titleWidget); mainLayout->addLayout(midLayout); mainLayout->addWidget(buttonBox); setLayout(mainLayout); } } diff --git a/libs/widgetutils/xmlgui/kbugreport.cpp b/libs/widgetutils/xmlgui/kbugreport.cpp index 9d2cdebbfc..3ba99a225b 100644 --- a/libs/widgetutils/xmlgui/kbugreport.cpp +++ b/libs/widgetutils/xmlgui/kbugreport.cpp @@ -1,233 +1,233 @@ /* This file is part of the KDE project Copyright (C) 1999 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. */ #include "kbugreport.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 "systeminformation_p.h" #include "config-xmlgui.h" #include class KBugReportPrivate { public: KBugReportPrivate(KBugReport *q): q(q), m_aboutData(KAboutData::applicationData()) {} void _k_updateUrl(); KBugReport *q; QProcess *m_process; KAboutData m_aboutData; QTextEdit *m_lineedit; QLineEdit *m_subject; QLabel *m_version; QString m_strVersion; QGroupBox *m_bgSeverity; QComboBox *appcombo; QString lastError; QString appname; QString os; QUrl url; QList severityButtons; int currentSeverity() { for (int i = 0; i < severityButtons.count(); i++) if (severityButtons[i]->isChecked()) { return i; } return -1; } }; KBugReport::KBugReport(const KAboutData &aboutData, QWidget *_parent) : QDialog(_parent), d(new KBugReportPrivate(this)) { setWindowTitle(i18n("Submit Bug Report")); QDialogButtonBox *buttonBox = new QDialogButtonBox(this); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); d->m_aboutData = aboutData; d->m_process = 0; KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::close()); QLabel *tmpLabel; QVBoxLayout *lay = new QVBoxLayout; setLayout(lay); KTitleWidget *title = new KTitleWidget(this); title->setText(i18n("Submit Bug Report")); title->setPixmap(KisIconUtils::loadIcon(QStringLiteral("tools-report-bug")).pixmap(32)); lay->addWidget(title); QGridLayout *glay = new QGridLayout(); lay->addLayout(glay); int row = 0; // Program name QString qwtstr = i18n("The application for which you wish to submit a bug report - if incorrect, please use the Report Bug menu item of the correct application"); tmpLabel = new QLabel(i18n("Application: "), this); glay->addWidget(tmpLabel, row, 0); tmpLabel->setWhatsThis(qwtstr); d->appcombo = new QComboBox(this); d->appcombo->setWhatsThis(qwtstr); QStringList packageList = QStringList() << "krita"; d->appcombo->addItems(packageList); connect(d->appcombo, SIGNAL(activated(int)), SLOT(_k_appChanged(int))); d->appname = d->m_aboutData.productName(); glay->addWidget(d->appcombo, row, 1); int index = 0; for (; index < d->appcombo->count(); index++) { if (d->appcombo->itemText(index) == d->appname) { break; } } if (index == d->appcombo->count()) { // not present d->appcombo->addItem(d->appname); } d->appcombo->setCurrentIndex(index); tmpLabel->setWhatsThis(qwtstr); // Version qwtstr = i18n("The version of this application - please make sure that no newer version is available before sending a bug report"); tmpLabel = new QLabel(i18n("Version:"), this); glay->addWidget(tmpLabel, ++row, 0); tmpLabel->setWhatsThis(qwtstr); d->m_strVersion = d->m_aboutData.version(); if (d->m_strVersion.isEmpty()) { d->m_strVersion = i18n("no version set (programmer error)"); } d->m_version = new QLabel(d->m_strVersion, this); d->m_version->setTextInteractionFlags(Qt::TextBrowserInteraction); //glay->addWidget( d->m_version, row, 1 ); glay->addWidget(d->m_version, row, 1, 1, 2); d->m_version->setWhatsThis(qwtstr); tmpLabel = new QLabel(i18n("OS:"), this); glay->addWidget(tmpLabel, ++row, 0); d->os = SystemInformation::operatingSystemVersion(); tmpLabel = new QLabel(d->os, this); tmpLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); glay->addWidget(tmpLabel, row, 1, 1, 2); tmpLabel = new QLabel(i18n("Compiler:"), this); glay->addWidget(tmpLabel, ++row, 0); tmpLabel = new QLabel(QString::fromLatin1(XMLGUI_COMPILER_VERSION), this); tmpLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); glay->addWidget(tmpLabel, row, 1, 1, 2); // Point to the web form lay->addSpacing(10); QString text = i18n("" "

    Please read this guide for reporting bugs first!

    " "

    To submit a bug report, click on the button below. This will open a web browser " - "window on http://bugs.kde.org where you will find " + "window on https://bugs.kde.org where you will find " "a form to fill in. The information displayed above will be transferred to that server.

    "); QLabel *label = new QLabel(text, this); label->setOpenExternalLinks(true); label->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard); label->setWordWrap(true); lay->addWidget(label); lay->addSpacing(10); d->appcombo->setFocus(); d->_k_updateUrl(); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setText(i18n("&Submit Bug Report")); okButton->setIcon(KisIconUtils::loadIcon(QStringLiteral("tools-report-bug"))); lay->addWidget(buttonBox); setMinimumHeight(sizeHint().height() + 20); // WORKAROUND: prevent "cropped" qcombobox } KBugReport::~KBugReport() { delete d; } void KBugReportPrivate::_k_updateUrl() { url = QUrl(QStringLiteral("https://bugs.kde.org/enter_bug.cgi")); QUrlQuery query; query.addQueryItem(QStringLiteral("format"), QLatin1String("guided")); // use the guided form // the string format is product/component, where component is optional QStringList list = appcombo->currentText().split(QLatin1Char('/')); query.addQueryItem(QStringLiteral("product"), list[0]); if (list.size() == 2) { query.addQueryItem(QStringLiteral("component"), list[1]); } query.addQueryItem(QStringLiteral("version"), m_strVersion); // TODO: guess and fill OS(sys_os) and Platform(rep_platform) fields #ifdef Q_OS_WIN query.addQueryItem(QStringLiteral("op_sys"), QStringLiteral("MS Windows")); query.addQueryItem(QStringLiteral("rep_platform"), QStringLiteral("MS Windows")); #endif url.setQuery(query); } void KBugReport::accept() { QDesktopServices::openUrl(d->url); } #include "moc_kbugreport.cpp" diff --git a/libs/widgetutils/xmlgui/kxmlguibuilder.cpp b/libs/widgetutils/xmlgui/kxmlguibuilder.cpp index c4944d55ec..532c27fdec 100644 --- a/libs/widgetutils/xmlgui/kxmlguibuilder.cpp +++ b/libs/widgetutils/xmlgui/kxmlguibuilder.cpp @@ -1,433 +1,433 @@ /* This file is part of the KDE project Copyright (C) 2000 Simon Hausmann 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. */ #include "kxmlguibuilder.h" #include "kxmlguiclient.h" #include "ktoolbar.h" #include "kmainwindow.h" #include "kxmlguiwindow.h" #include "kmenumenuhandler_p.h" #include #include #include #include #include #include #include #include #include #include #include #if defined(KCONFIG_BEFORE_5_24) # define authorizeAction authorizeKAction #endif using namespace KDEPrivate; class KXMLGUIBuilderPrivate { public: - KXMLGUIBuilderPrivate() : m_client(0L) {} + KXMLGUIBuilderPrivate() { } ~KXMLGUIBuilderPrivate() { } - QWidget *m_widget; + QWidget *m_widget {0}; QString tagMainWindow; QString tagMenuBar; QString tagMenu; QString tagToolBar; QString tagStatusBar; QString tagSeparator; QString tagTearOffHandle; QString tagMenuTitle; QString attrName; QString attrLineSeparator; QString attrDomain; QString attrText1; QString attrText2; QString attrContext; QString attrIcon; - KXMLGUIClient *m_client; + KXMLGUIClient *m_client {0}; - KMenuMenuHandler *m_menumenuhandler; + KMenuMenuHandler *m_menumenuhandler {0}; }; KXMLGUIBuilder::KXMLGUIBuilder(QWidget *widget) : d(new KXMLGUIBuilderPrivate) { d->m_widget = widget; d->tagMainWindow = QStringLiteral("mainwindow"); d->tagMenuBar = QStringLiteral("menubar"); d->tagMenu = QStringLiteral("menu"); d->tagToolBar = QStringLiteral("toolbar"); d->tagStatusBar = QStringLiteral("statusbar"); d->tagSeparator = QStringLiteral("separator"); d->tagTearOffHandle = QStringLiteral("tearoffhandle"); d->tagMenuTitle = QStringLiteral("title"); d->attrName = QStringLiteral("name"); d->attrLineSeparator = QStringLiteral("lineseparator"); d->attrDomain = QStringLiteral("translationDomain"); d->attrText1 = QStringLiteral("text"); d->attrText2 = QStringLiteral("Text"); d->attrContext = QStringLiteral("context"); d->attrIcon = QStringLiteral("icon"); d->m_menumenuhandler = new KMenuMenuHandler(this); } KXMLGUIBuilder::~KXMLGUIBuilder() { delete d->m_menumenuhandler; delete d; } QWidget *KXMLGUIBuilder::widget() { return d->m_widget; } QStringList KXMLGUIBuilder::containerTags() const { QStringList res; res << d->tagMenu << d->tagToolBar << d->tagMainWindow << d->tagMenuBar << d->tagStatusBar; return res; } QWidget *KXMLGUIBuilder::createContainer(QWidget *parent, int index, const QDomElement &element, QAction *&containerAction) { containerAction = 0; if (element.attribute(QStringLiteral("deleted")).toLower() == QLatin1String("true")) { return 0; } const QString tagName = element.tagName().toLower(); if (tagName == d->tagMainWindow) { KMainWindow *mainwindow = qobject_cast(d->m_widget); // could be 0 return mainwindow; } if (tagName == d->tagMenuBar) { KMainWindow *mainWin = qobject_cast(d->m_widget); QMenuBar *bar = 0; if (mainWin) { bar = mainWin->menuBar(); } if (!bar) { bar = new QMenuBar(d->m_widget); } bar->show(); return bar; } if (tagName == d->tagMenu) { // Look up to see if we are inside a mainwindow. If yes, then // use it as parent widget (to get kaction to plug itself into the // mainwindow). Don't use a popupmenu as parent widget, otherwise // the popup won't be hidden if it is used as a standalone menu as well. // // Note: menus with a parent of 0, coming from child clients, can be // leaked if the child client is deleted without a proper removeClient call, though. QWidget *p = parent; if (!p && qobject_cast(d->m_widget)) { p = d->m_widget; } while (p && !qobject_cast(p)) { p = p->parentWidget(); } QString name = element.attribute(d->attrName); if (!KAuthorized::authorizeAction(name)) { return 0; } QMenu *popup = new QMenu(p); popup->setObjectName(name); d->m_menumenuhandler->insertMenu(popup); QString i18nText; QDomElement textElem = element.namedItem(d->attrText1).toElement(); if (textElem.isNull()) { // try with capital T textElem = element.namedItem(d->attrText2).toElement(); } const QString text = textElem.text(); const QString context = textElem.attribute(d->attrContext); //qDebug(260) << "DOMAIN" << KLocalizedString::applicationDomain(); //qDebug(260) << "ELEMENT TEXT:" << text; if (text.isEmpty()) { // still no luck i18nText = i18n("No text"); } else { QByteArray domain = textElem.attribute(d->attrDomain).toUtf8(); if (domain.isEmpty()) { domain = element.ownerDocument().documentElement().attribute(d->attrDomain).toUtf8(); if (domain.isEmpty()) { domain = KLocalizedString::applicationDomain(); } } if (context.isEmpty()) { i18nText = i18nd(domain.constData(), text.toUtf8().constData()); } else { i18nText = i18ndc(domain.constData(), context.toUtf8().constData(), text.toUtf8().constData()); } } //qDebug(260) << "ELEMENT i18n TEXT:" << i18nText; const QString icon = element.attribute(d->attrIcon); QIcon pix; if (!icon.isEmpty()) { pix = KisIconUtils::loadIcon(icon); } if (parent) { QAction *act = popup->menuAction(); if (!icon.isEmpty()) { act->setIcon(pix); } act->setText(i18nText); if (index == -1 || index >= parent->actions().count()) { parent->addAction(act); } else { parent->insertAction(parent->actions().value(index), act); } containerAction = act; containerAction->setObjectName(name); } return popup; } if (tagName == d->tagToolBar) { QString name = element.attribute(d->attrName); KToolBar *bar = static_cast(d->m_widget->findChild(name)); if (!bar) { bar = new KToolBar(name, d->m_widget, false); } if (qobject_cast(d->m_widget)) { if (d->m_client && !d->m_client->xmlFile().isEmpty()) { bar->addXMLGUIClient(d->m_client); } } bar->loadState(element); return bar; } if (tagName == d->tagStatusBar) { KMainWindow *mainWin = qobject_cast(d->m_widget); if (mainWin) { mainWin->statusBar()->show(); return mainWin->statusBar(); } QStatusBar *bar = new QStatusBar(d->m_widget); return bar; } return 0L; } void KXMLGUIBuilder::removeContainer(QWidget *container, QWidget *parent, QDomElement &element, QAction *containerAction) { // Warning parent can be 0L if (qobject_cast(container)) { if (parent) { parent->removeAction(containerAction); } delete container; } else if (qobject_cast(container)) { KToolBar *tb = static_cast(container); tb->saveState(element); delete tb; } else if (qobject_cast(container)) { QMenuBar *mb = static_cast(container); mb->hide(); // Don't delete menubar - it can be reused by createContainer. // If you decide that you do need to delete the menubar, make // sure that QMainWindow::d->mb does not point to a deleted // menubar object. } else if (qobject_cast(container)) { if (qobject_cast(d->m_widget)) { container->hide(); } else { delete static_cast(container); } } else { qWarning() << "Unhandled container to remove : " << container->metaObject()->className(); } } QStringList KXMLGUIBuilder::customTags() const { QStringList res; res << d->tagSeparator << d->tagTearOffHandle << d->tagMenuTitle; return res; } QAction *KXMLGUIBuilder::createCustomElement(QWidget *parent, int index, const QDomElement &element) { QAction *before = 0L; if (index > 0 && index < parent->actions().count()) { before = parent->actions().at(index); } const QString tagName = element.tagName().toLower(); if (tagName == d->tagSeparator) { if (QMenu *menu = qobject_cast(parent)) { // QMenu already cares for leading/trailing/repeated separators // no need to check anything return menu->insertSeparator(before); } else if (QMenuBar *bar = qobject_cast(parent)) { QAction *separatorAction = new QAction(bar); separatorAction->setSeparator(true); bar->insertAction(before, separatorAction); return separatorAction; } else if (KToolBar *bar = qobject_cast(parent)) { /* FIXME KAction port - any need to provide a replacement for lineSeparator/normal separator? bool isLineSep = true; QDomNamedNodeMap attributes = element.attributes(); unsigned int i = 0; for (; i < attributes.length(); i++ ) { QDomAttr attr = attributes.item( i ).toAttr(); if ( attr.name().toLower() == d->attrLineSeparator && attr.value().toLower() == QStringLiteral("false") ) { isLineSep = false; break; } } if ( isLineSep ) return bar->insertSeparator( index ? bar->actions()[index - 1] : 0L ); else*/ return bar->insertSeparator(before); } } else if (tagName == d->tagTearOffHandle) { static_cast(parent)->setTearOffEnabled(true); } else if (tagName == d->tagMenuTitle) { if (QMenu *m = qobject_cast(parent)) { QString i18nText; const QString text = element.text(); if (text.isEmpty()) { i18nText = i18n("No text"); } else { QByteArray domain = element.attribute(d->attrDomain).toUtf8(); if (domain.isEmpty()) { domain = element.ownerDocument().documentElement().attribute(d->attrDomain).toUtf8(); if (domain.isEmpty()) { domain = KLocalizedString::applicationDomain(); } } i18nText = i18nd(domain.constData(), qPrintable(text)); } QString icon = element.attribute(d->attrIcon); QIcon pix; if (!icon.isEmpty()) { pix = KisIconUtils::loadIcon(icon); } if (!icon.isEmpty()) { return m->insertSection(before, pix, i18nText); } else { return m->insertSection(before, i18nText); } } } QAction *blank = new QAction(parent); blank->setVisible(false); parent->insertAction(before, blank); return blank; } void KXMLGUIBuilder::removeCustomElement(QWidget *parent, QAction *action) { parent->removeAction(action); } KXMLGUIClient *KXMLGUIBuilder::builderClient() const { return d->m_client; } void KXMLGUIBuilder::setBuilderClient(KXMLGUIClient *client) { d->m_client = client; } void KXMLGUIBuilder::finalizeGUI(KXMLGUIClient *) { KXmlGuiWindow *window = qobject_cast(d->m_widget); if (!window) { return; } #if 0 KToolBar *toolbar = 0; QListIterator it(((KMainWindow *)d->m_widget)->toolBarIterator()); while ((toolbar = it.current())) { //qDebug(260) << "KXMLGUIBuilder::finalizeGUI toolbar=" << (void*)toolbar; ++it; toolbar->positionYourself(); } #else window->finalizeGUI(false); #endif } void KXMLGUIBuilder::virtual_hook(int, void *) { /*BASE::virtual_hook( id, data );*/ } diff --git a/packaging/android/apk/AndroidManifest.xml b/packaging/android/apk/AndroidManifest.xml index 6903d72053..960fc3957d 100644 --- a/packaging/android/apk/AndroidManifest.xml +++ b/packaging/android/apk/AndroidManifest.xml @@ -1,87 +1,87 @@ - + diff --git a/packaging/android/fdroid/build.gradle b/packaging/android/fdroid/build.gradle new file mode 100644 index 0000000000..dab0cd85b6 --- /dev/null +++ b/packaging/android/fdroid/build.gradle @@ -0,0 +1,5 @@ +// Hack! +// This is a placeholder file, the real build.gradle is in $KRITA_SRC/packaging/android/apk/ +// without this f-droid build will fail. +task clean() {} +task assembleRelease() {} diff --git a/packaging/linux/flatpak/org.kde.krita-nightly.json b/packaging/linux/flatpak/org.kde.krita-nightly.json deleted file mode 100644 index 304487b6c1..0000000000 --- a/packaging/linux/flatpak/org.kde.krita-nightly.json +++ /dev/null @@ -1,355 +0,0 @@ -{ - "app-id": "org.kde.krita", - "branch": "master", - "runtime": "org.kde.Platform", - "runtime-version": "5.9", - "sdk": "org.kde.Sdk", - "command": "krita", - "rename-icon": "krita", - "tags": [ - "nightly" - ], - "desktop-file-name-prefix": "(Nightly) ", - "finish-args": [ - "--share=ipc", - "--socket=x11", - "--share=network", - "--device=dri", - "--filesystem=home", - "--env=PYTHONPATH=/app/lib/python3/dist-packages" - ], - "cleanup": [ - "/include", - "/lib/pkgconfig", - "/lib/cmake", - "/share/aclocal", - "/share/pkgconfig", - "*.la", - "*.cmake" - ], - "modules": [ - { - "name": "boost", - "buildsystem": "simple", - "build-commands": [ - "./bootstrap.sh --prefix=/app --with-libraries=system", - "./b2 -j `nproc` install" - ], - "cleanup": [ - "*.a" - ], - "sources": [ - { - "type": "archive", - "url": "https://sourceforge.net/projects/boost/files/boost/1.63.0/boost_1_63_0.tar.bz2", - "sha256": "beae2529f759f6b3bf3f4969a19c2e9d6f0c503edcb2de4a61d1428519fcb3b0" - } - ] - }, - { - "name": "eigen", - "buildsystem": "cmake-ninja", - "builddir": true, - "config-opts": [ - "-DCMAKE_BUILD_TYPE=Release" - ], - "cleanup": [ - "/share" - ], - "sources": [ - { - "type": "archive", - "url": "https://bitbucket.org/eigen/eigen/get/3.3.4.tar.bz2", - "sha256": "dd254beb0bafc695d0f62ae1a222ff85b52dbaa3a16f76e781dce22d0d20a4a6" - } - ] - }, - { - "name": "exiv2", - "buildsystem": "cmake-ninja", - "builddir": true, - "config-opts": [ - "-DCMAKE_BUILD_TYPE=Release" - ], - "cleanup": [ - "/bin", - "*.a", - "/share/man" - ], - "sources": [ - { - "type": "archive", - "url": "http://www.exiv2.org/builds/exiv2-0.26-trunk.tar.gz", - "sha256": "c75e3c4a0811bf700d92c82319373b7a825a2331c12b8b37d41eb58e4f18eafb" - } - ] - }, - { - "name": "ilmbase", - "config-opts": [ - "--disable-static" - ], - "sources": [ - { - "type": "archive", - "url": "https://download.savannah.nongnu.org/releases/openexr/ilmbase-2.2.1.tar.gz", - "sha256": "cac206e63be68136ef556c2b555df659f45098c159ce24804e9d5e9e0286609e" - } - ] - }, - { - "name": "openexr", - "config-opts": [ - "--disable-static" - ], - "cleanup": [ - "/bin", - "/share/doc" - ], - "sources": [ - { - "type": "archive", - "url": "https://download.savannah.nongnu.org/releases/openexr/openexr-2.2.1.tar.gz", - "sha256": "8f9a5af6131583404261931d9a5c83de0a425cb4b8b25ddab2b169fbf113aecd" - } - ] - }, - { - "name": "libraw", - "config-opts": [ - "--disable-static" - ], - "cleanup": [ - "/bin", - "/share/doc" - ], - "sources": [ - { - "type": "archive", - "url": "https://www.libraw.org/data/LibRaw-0.18.6.tar.gz", - "sha256": "e5b8acca558aa457bc9214802004320c5610d1434c2adb1f3ea367f026afa53b" - } - ] - }, - { - "name": "fftw", - "config-opts": [ - "--disable-static", - "--enable-shared", - "--disable-doc", - "--enable-threads" - ], - "cleanup": [ - "/bin", - "/share/man" - ], - "sources": [ - { - "type": "archive", - "url": "http://www.fftw.org/fftw-3.3.7.tar.gz", - "sha256": "3b609b7feba5230e8f6dd8d245ddbefac324c5a6ae4186947670d9ac2cd25573" - } - ] - }, - { - "name": "opencolorio", - "buildsystem": "cmake", - "builddir": true, - "build-options": { - "arch": { - "arm": { - "config-opts": [ - "-DOCIO_USE_SSE=OFF" - ] - }, - "aarch64": { - "config-opts": [ - "-DOCIO_USE_SSE=OFF" - ] - } - } - }, - "config-opts": [ - "-DCMAKE_BUILD_TYPE=Release", - "-DOCIO_BUILD_STATIC=OFF" - ], - "cleanup": [ - "/bin" - ], - "sources": [ - { - "type": "archive", - "url": "https://github.com/imageworks/OpenColorIO/archive/v1.0.9.tar.gz", - "sha256": "27c81e691c15753cd2b560c2ca4bd5679a60c2350eedd43c99d44ca25d65ea7f" - } - ] - }, - { - "name": "vc", - "skip-arches": [ - "aarch64", - "arm" - ], - "buildsystem": "cmake-ninja", - "builddir": true, - "config-opts": [ - "-DCMAKE_BUILD_TYPE=Release" - ], - "cleanup": [ - "*.a" - ], - "sources": [ - { - "type": "archive", - "url": "https://github.com/VcDevel/Vc/releases/download/1.3.3/Vc-1.3.3.tar.gz", - "sha256": "08c629d2e14bfb8e4f1a10f09535e4a3c755292503c971ab46637d2986bdb4fe" - }, - { - "type": "shell", - "commands": [ - "sed -i 's/x86|/x86|i686|/' CMakeLists.txt" - ] - } - ] - }, - { - "name": "poppler-data", - "buildsystem": "cmake-ninja", - "builddir": true, - "config-opts": [ - "-DCMAKE_BUILD_TYPE=Release" - ], - "sources": [ - { - "type": "archive", - "url": "https://poppler.freedesktop.org/poppler-data-0.4.8.tar.gz", - "sha256": "1096a18161f263cccdc6d8a2eb5548c41ff8fcf9a3609243f1b6296abdf72872" - } - ] - }, - { - "name": "poppler", - "buildsystem": "cmake-ninja", - "builddir": true, - "config-opts": [ - "-DCMAKE_BUILD_TYPE=Release", - "-DBUILD_GTK_TESTS=OFF", - "-DBUILD_QT5_TESTS=OFF", - "-DBUILD_CPP_TESTS=OFF", - "-DENABLE_UTILS=OFF", - "-DENABLE_CPP=OFF", - "-DENABLE_GLIB=OFF" - ], - "sources": [ - { - "type": "archive", - "url": "https://poppler.freedesktop.org/poppler-0.62.0.tar.xz", - "sha256": "5b9a73dfd4d6f61d165ada1e4f0abd2d420494bf9d0b1c15d0db3f7b83a729c6" - } - ] - }, - { - "name": "gsl", - "config-opts": [ - "--disable-static" - ], - "cleanup": [ - "/bin", - "/share/info", - "/share/man" - ], - "sources": [ - { - "type": "archive", - "url": "https://ftpmirror.gnu.org/gnu/gsl/gsl-2.4.tar.gz", - "sha256": "4d46d07b946e7b31c19bbf33dda6204d7bedc2f5462a1bae1d4013426cd1ce9b" - } - ] - }, - { - "name": "gmic-qt", - "buildsystem": "cmake-ninja", - "builddir": true, - "config-opts": [ - "-DCMAKE_BUILD_TYPE=Release", - "-DGMIC_QT_HOST=krita", - "-DGMIC_PATH=./gmic/src" - ], - "sources": [ - { - "type": "archive", - "url": "https://github.com/c-koi/gmic-qt/archive/v.218.tar.gz", - "sha256": "697861e5a1e024a3ccf9c96e513ec9f60f65d6e3c438ba355afcd10839b06f39" - }, - { - "type": "file", - "url": "https://gmic.eu/files/source/gmic_2.1.8.tar.gz", - "sha256": "f22783f14cb202dec4a840733f2028f6e2c464fdd2f0166fc38943702cea6bde", - "dest-filename": "gmic.tar.gz" - }, - { - "type": "shell", - "commands": [ - "tar xf gmic.tar.gz", - "mv gmic-* gmic" - ] - } - ], - "post-install": [ - "install -Dm755 gmic_krita_qt /app/bin/gmic_krita_qt" - ] - }, - { - "name": "sip", - "buildsystem": "simple", - "build-commands": [ - "python3 configure.py --bindir=/app/bin --destdir=/app/lib/python3/dist-packages --incdir=/app/include/python3 --sipdir=/app/share/sip --stubsdir=/app/lib/python3/dist-packages", - "make -j `nproc`", - "make install" - ], - "sources": [ - { - "type": "archive", - "url": "https://sourceforge.net/projects/pyqt/files/sip/sip-4.19.6/sip-4.19.6.tar.gz", - "sha256": "9dda27ae181bea782ebc8768d29f22f85ab6e5128ee3ab21f491febad707925a" - } - ] - }, - { - "name": "pyqt", - "buildsystem": "simple", - "build-commands": [ - "python3 configure.py --confirm-license --sip-incdir=/app/include/python3 --bindir=/app/bin --destdir=/app/lib/python3/dist-packages --designer-plugindir=/app/lib/plugins/designer --qml-plugindir=/app/lib/plugins/PyQt5 --sipdir=/app/share/sip --stubsdir=/app/lib/python3/dist-packages/PyQt5", - "make -j `nproc`", - "make install" - ], - "sources": [ - { - "type": "archive", - "url": "https://sourceforge.net/projects/pyqt/files/PyQt5/PyQt-5.9.2/PyQt5_gpl-5.9.2.tar.gz", - "sha256": "c190dac598c97b0113ca5e7a37c71c623f02d1d713088addfacac4acfa4b8394" - } - ] - }, - { - "name": "krita", - "buildsystem": "cmake-ninja", - "builddir": true, - "build-options": { - "env": { - "PYTHONPATH": "/app/lib/python3/dist-packages" - } - }, - "config-opts": [ - "-DCMAKE_BUILD_TYPE=RelWithDebInfo" - ], - "sources": [ - { - "type": "git", - "url": "git://anongit.kde.org/krita.git", - "branch": "master" - } - ] - } - ] -} diff --git a/packaging/linux/snap/build_in_container.sh b/packaging/linux/snap/build_in_container.sh index 0edfffe8c5..afc3d0f20d 100755 --- a/packaging/linux/snap/build_in_container.sh +++ b/packaging/linux/snap/build_in_container.sh @@ -1,19 +1,19 @@ #!/bin/bash set -ex cd /workspace/snap ping -c1 networkcheck.kde.org apt-key adv --keyserver keyserver.ubuntu.com --recv E6D4736255751E5D echo 'deb http://archive.neon.kde.org/unstable bionic main' > /etc/apt/sources.list.d/neon.list apt update -snap install --edge --classic snapcraft +snap install --classic snapcraft snapcraft --version snapcraft --destructive-mode mkdir -p /workspace/result mv *.snap /workspace/result/ diff --git a/packaging/linux/snap/snapcraft.yaml b/packaging/linux/snap/snapcraft.yaml index 6a71aa7a57..6165c1163f 100644 --- a/packaging/linux/snap/snapcraft.yaml +++ b/packaging/linux/snap/snapcraft.yaml @@ -1,179 +1,173 @@ name: krita -version: 4.2.7.1 -summary: Krita is the digital painting studio for artists -description: Krita is a creative application for raster images. Whether you want to create - from scratch or work with existing images, Krita is for you. You can work with - photos or scanned images, or start with a blank slate. Krita supports most - graphics tablets out of the box. +version: 4.2.8.2 +adopt-info: krita + base: core18 +confinement: strict apps: krita: + common-id: org.kde.krita command: usr/bin/krita command-chain: - bin/qt5-launch plugs: [x11, unity7, home, opengl, network, network-bind, removable-media, desktop, desktop-legacy] - desktop: usr/share/applications/org.kde.krita.desktop layout: /usr/bin/ffmpeg: bind-file: $SNAP/usr/bin/ffmpeg parts: krita: plugin: cmake configflags: - "-DCMAKE_INSTALL_PREFIX=/usr" - "-DCMAKE_BUILD_TYPE=Release" - "-DENABLE_TESTING=OFF" - "-DBUILD_TESTING=OFF" - "-DHIDE_SAFE_ASSERTS=OFF" - "-DKDE_SKIP_TEST_SETTINGS=ON" - source: https://download.kde.org/stable/krita/4.2.7.1/krita-4.2.7.1.tar.xz -# Use these instead to build from the git source -# source: https://anongit.kde.org/krita.git -# source-type: git -# source-branch: master - override-stage: | - # Stage the part - snapcraftctl stage - # Modify the .desktop file in the stage to point to where the icon will be in the installed snap - sed -i 's|Icon=\(.*\)|Icon=${SNAP}/usr/share/icons/hicolor/1024x1024/apps/\1.png|' usr/share/applications/org.kde.krita.desktop + source: https://download.kde.org/stable/krita/4.2.8/krita-$SNAPCRAFT_PROJECT_VERSION.tar.xz + # Use these instead to build from the git source + # source: https://anongit.kde.org/krita.git + # source-type: git + # source-branch: master + parse-info: ["usr/share/metainfo/org.kde.krita.appdata.xml"] build-packages: - gettext - build-essential - cmake - libboost-dev - libboost-system-dev - libeigen3-dev - libexiv2-dev - libfftw3-dev - libfontconfig1-dev - libfreetype6-dev - libgl1-mesa-dev - libglew-dev - libglib2.0-dev - libglu1-mesa-dev - libgsf-1-dev - libgsl-dev - libjpeg-dev - liblcms2-dev - libopenexr-dev - libpng-dev - libpoppler-qt5-dev - - libopenjpeg-dev + - libopenjp2-7-dev - libtiff5-dev - libvc-dev - libopencolorio-dev - libx11-dev - libxml2-dev - libxslt1-dev - libxi-dev - pkg-config - vc-dev - zlib1g-dev - libkf5kdcraw-dev - shared-mime-info - libopenimageio-dev - extra-cmake-modules - libkf5archive-dev - libkf5coreaddons-dev - libkf5guiaddons-dev - libkf5itemmodels-dev - libkf5itemviews-dev - libkf5widgetsaddons-dev - libkf5i18n-dev - libkf5windowsystem-dev - libkf5completion-dev - libkf5iconthemes-dev - libkf5kiocore5 - libqt5svg5-dev - libqt5x11extras5-dev - libqt5opengl5-dev - libquazip5-dev - qtmultimedia5-dev - qtdeclarative5-dev - libkf5crash-dev runtime: plugin: nil stage-packages: - libexiv2-27 - libfftw3-double3 - libgomp1 - libgsl23 - libilmbase12 - libjpeg8 - liblcms2-2 - libopencolorio1v5 - libopenexr22 - libpng16-16 - libstdc++6 - libtiff5 - libx11-6 - libxcb1 - libxi6 - zlib1g - libpoppler-qt5-1 - shared-mime-info - libboost-system1.65.1 - librtmp1 - libqt5quickwidgets5 - libkf5archive5 - libkf5completion5 - libkf5configcore5 - libkf5configgui5 - libkf5coreaddons5 - libkf5guiaddons5 - libkf5i18n5 - libkf5itemviews5 - libkf5widgetsaddons5 - libkf5windowsystem5 - libkf5crash5 - libqt5concurrent5 - libqt5core5a - libqt5dbus5 - libqt5gui5 - libqt5network5 - libqt5printsupport5 - libqt5svg5 - libqt5widgets5 - libqt5x11extras5 - libqt5xml5 - locales - libc-bin - libquazip5-1 - libqt5quick5 - libqt5quickparticles5 - libqt5quickshapes5 - qt5-qmltooling-plugins - libqt5qml5 - libqt5multimedia5 - libqt5multimediagsttools5 - libqt5multimediaquick5 - libqt5multimediawidgets5 # Required for rendering animations - ffmpeg - libglu1-mesa - libslang2 prime: - "-usr/share/wallpapers/*" - "-usr/share/fonts/*" plasma-integration: plugin: nil stage-packages: - plasma-integration - kde-style-breeze - breeze-icon-theme - kio # runtime slaves for kio prime: - "-usr/share/wallpapers/*" - "-usr/share/fonts/*" launcher: plugin: dump source: scripts organize: qt5-launch: bin/qt5-launch kf5-locale-gen: bin/kf5-locale-gen diff --git a/packaging/macos/osxbuild.sh b/packaging/macos/osxbuild.sh index ec39bee74e..5423510ac9 100755 --- a/packaging/macos/osxbuild.sh +++ b/packaging/macos/osxbuild.sh @@ -1,536 +1,536 @@ #!/usr/bin/env bash # osxbuild.sh automates building and installing of krita and krita dependencies # for OSX, the script only needs you to set BUILDROOT environment to work # properly. # # Run with no args for a short help about each command. # builddeps: Attempts to build krita dependencies in the necessary order, # intermediate steps for creating symlinks and fixing rpath of some # packages midway is also managed. Order goes from top to bottom, to add # new steps just place them in the proper place. # rebuilddeps: This re-runs all make and make install of dependencies 3rdparty # this was needed as deleting the entire install directory an rerunning build # step for dependencies does not install if they are already built. This step # forces installation. Have not tested it lately so it might not be needed anymore # build: Runs cmake build and make step for krita sources. It always run cmake step, so # it might take a bit longer than a pure on the source tree. The script tries # to set the make flag -jN to a proper N. # install: Runs install step for krita sources. # fixboost: Search for all libraries using boost and sets a proper @rpath for boost as by # default it fails to set a proper @rpath # buildinstall: Runs build, install and fixboost steps.# if test -z $BUILDROOT; then echo "ERROR: BUILDROOT env not set, exiting!" echo "\t Must point to the root of the buildfiles as stated in 3rdparty Readme" exit fi echo "BUILDROOT set to ${BUILDROOT}" export KIS_SRC_DIR=${BUILDROOT}/krita export KIS_TBUILD_DIR=${BUILDROOT}/depbuild export KIS_TDEPINSTALL_DIR=${BUILDROOT}/depinstall export KIS_DOWN_DIR=${BUILDROOT}/down export KIS_BUILD_DIR=${BUILDROOT}/kisbuild export KIS_INSTALL_DIR=${BUILDROOT}/i # flags for OSX environment # Qt only supports from 10.12 up, and https://doc.qt.io/qt-5/macos.html#target-platforms warns against setting it lower export MACOSX_DEPLOYMENT_TARGET=10.12 export QMAKE_MACOSX_DEPLOYMENT_TARGET=10.12 # Build time variables if test -z $(which cmake); then echo "ERROR: cmake not found, exiting!" exit fi export PATH=${KIS_INSTALL_DIR}/bin:$PATH export C_INCLUDE_PATH=${KIS_INSTALL_DIR}/include:/usr/include:${C_INCLUDE_PATH} export CPLUS_INCLUDE_PATH=${KIS_INSTALL_DIR}/include:/usr/include:${CPLUS_INCLUDE_PATH} export LIBRARY_PATH=${KIS_INSTALL_DIR}/lib:/usr/lib:${LIBRARY_PATH} # export CPPFLAGS=-I${KIS_INSTALL_DIR}/include # export LDFLAGS=-L${KIS_INSTALL_DIR}/lib export FRAMEWORK_PATH=${KIS_INSTALL_DIR}/lib/ # export PYTHONHOME=${KIS_INSTALL_DIR} # export PYTHONPATH=${KIS_INSTALL_DIR}/sip:${KIS_INSTALL_DIR}/lib/python3.5/site-packages:${KIS_INSTALL_DIR}/lib/python3.5 # This will make the debug output prettier export KDE_COLOR_DEBUG=1 export QTEST_COLORED=1 export OUPUT_LOG="${BUILDROOT}/osxbuild.log" export ERROR_LOG="${BUILDROOT}/osxbuild-error.log" printf "" > "${OUPUT_LOG}" printf "" > "${ERROR_LOG}" # configure max core for make compile ((MAKE_THREADS=1)) if test ${OSTYPE} == "darwin*"; then ((MAKE_THREADS = $(sysctl -n hw.ncpu) - 1)) fi # Prints stderr and stdout to log files # >(tee) works but breaks sigint log_cmd () { if [[ "${VERBOSE}" ]]; then "$@" 1>> ${OUPUT_LOG} 2>> ${ERROR_LOG} else "$@" 2>> ${ERROR_LOG} | tee -a ${OUPUT_LOG} > /dev/null fi } # Log messages to logfile log () { if [[ "${VERBOSE}" ]]; then printf "%s\n" "${@}" | tee -a ${OUPUT_LOG} else printf "%s\n" "${@}" | tee -a ${OUPUT_LOG} > /dev/null fi } # if previous command gives error # print msg print_if_error() { error_stat="${?}" if [ ${error_stat} -ne 0 ]; then printf "\e[31m%s %s\e[0m\n" "Error:" "${1}" 2>> ${ERROR_LOG} printf "%s\r" "${error_stat}" fi } # print status messages print_msg() { printf "\e[32m%s\e[0m\n" "${1}" printf "%s\n" "${1}" >> ${OUPUT_LOG} } check_dir_path () { printf "%s" "Checking if ${1} exists and is dir... " if test -d ${1}; then echo -e "OK" elif test -e ${1}; then echo -e "\n\tERROR: file ${1} exists but is not a directory!" >&2 return 1 else echo -e "Creating ${1}" mkdir ${1} fi return 0 } # builds dependencies for the first time cmake_3rdparty () { cd ${KIS_TBUILD_DIR} local build_pkgs=("${@}") # convert to array if [[ ${2} = "1" ]]; then local nofix="true" local build_pkgs=(${build_pkgs[@]:0:1}) fi for package in ${build_pkgs[@]} ; do print_msg "Building ${package}" log_cmd cmake --build . --config RelWithDebInfo --target ${package} if [[ ! $(print_if_error "Failed build ${package}") ]]; then print_msg "Build Success! ${package}" fi # fixes does not depend on failure if [[ ! ${nofix} ]]; then build_3rdparty_fixes ${package} fi done } build_3rdparty_fixes(){ pkg=${1} if [[ "${pkg}" = "ext_qt" && -e "${KIS_INSTALL_DIR}/bin/qmake" ]]; then ln -sf qmake "${KIS_INSTALL_DIR}/bin/qmake-qt5" elif [[ "${pkg}" = "ext_openexr" ]]; then # open exr will fail the first time is called # rpath needs to be fixed an build rerun log "Fixing rpath on openexr file: b44ExpLogTable" log "Fixing rpath on openexr file: dwaLookups" log_cmd install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_TBUILD_DIR}/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./b44ExpLogTable log_cmd install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_TBUILD_DIR}/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./dwaLookups # we must rerun build! cmake_3rdparty ext_openexr "1" elif [[ "${pkg}" = "ext_fontconfig" ]]; then log "fixing rpath on fc-cache" log_cmd install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_TBUILD_DIR}/ext_fontconfig/ext_fontconfig-prefix/src/ext_fontconfig-build/fc-cache/.libs/fc-cache # rerun rebuild cmake_3rdparty ext_fontconfig "1" fi } build_3rdparty () { print_msg "building in ${KIS_TBUILD_DIR}" log "$(check_dir_path ${KIS_TBUILD_DIR})" log "$(check_dir_path ${KIS_DOWN_DIR})" log "$(check_dir_path ${KIS_INSTALL_DIR})" cd ${KIS_TBUILD_DIR} log_cmd cmake ${KIS_SRC_DIR}/3rdparty/ \ -DCMAKE_OSX_DEPLOYMENT_TARGET=10.12 \ -DCMAKE_INSTALL_PREFIX=${KIS_INSTALL_DIR} \ -DEXTERNALS_DOWNLOAD_DIR=${KIS_DOWN_DIR} \ -DINSTALL_ROOT=${KIS_INSTALL_DIR} # -DCPPFLAGS=-I${KIS_INSTALL_DIR}/include \ # -DLDFLAGS=-L${KIS_INSTALL_DIR}/lib print_msg "finished 3rdparty build setup" if [[ -n ${1} ]]; then cmake_3rdparty "${@}" exit fi # build 3rdparty tools # The order must not be changed! cmake_3rdparty \ ext_pkgconfig \ ext_gettext \ ext_openssl \ ext_qt \ ext_zlib \ ext_boost \ ext_eigen3 \ ext_exiv2 \ ext_fftw3 \ ext_ilmbase \ ext_jpeg \ ext_lcms2 \ ext_ocio \ ext_openexr cmake_3rdparty \ ext_png \ ext_tiff \ ext_gsl \ ext_vc \ ext_libraw \ ext_giflib \ ext_freetype \ ext_fontconfig \ ext_poppler # Stop if qmake link was not created # this meant qt build fail and further builds will # also fail. log_cmd test -L "${KIS_INSTALL_DIR}/bin/qmake-qt5" if [[ $(print_if_error "qmake link missing!") ]]; then printf " link: ${KIS_INSTALL_DIR}/bin/qmake-qt5 missing! It probably means ext_qt failed!! check, fix and rerun!\n" exit fi # for python cmake_3rdparty \ ext_python \ ext_sip \ ext_pyqt cmake_3rdparty ext_libheif cmake_3rdparty \ ext_extra_cmake_modules \ ext_kconfig \ ext_kwidgetsaddons \ ext_kcompletion \ ext_kcoreaddons \ ext_kguiaddons \ ext_ki18n \ ext_kitemmodels \ ext_kitemviews \ ext_kimageformats \ ext_kwindowsystem \ ext_quazip } # Recall cmake for all 3rd party packages # make is only on target first run # subsequent runs only call make install rebuild_3rdparty () { print_msg "starting rebuild of 3rdparty packages" build_install_ext() { for pkg in ${@:1:${#@}}; do { cd ${KIS_TBUILD_DIR}/${pkg}/${pkg}-prefix/src/${pkg}-stamp } || { cd ${KIS_TBUILD_DIR}/ext_frameworks/${pkg}-prefix/src/${pkg}-stamp } || { cd ${KIS_TBUILD_DIR}/ext_heif/${pkg}-prefix/src/${pkg}-stamp } log "Installing ${pkg} files..." rm ${pkg}-configure ${pkg}-build ${pkg}-install cmake_3rdparty ${pkg} cd ${KIS_TBUILD_DIR} done } # Do not process complete list only pkgs given. if ! test -z ${1}; then build_install_ext ${@} exit fi build_install_ext \ ext_pkgconfig \ ext_iconv \ ext_gettext \ ext_openssl \ ext_qt \ ext_zlib \ ext_boost \ ext_eigen3 \ ext_expat \ ext_exiv2 \ ext_fftw3 \ ext_ilmbase \ ext_jpeg \ ext_patch \ ext_lcms2 \ ext_ocio \ ext_ilmbase \ ext_openexr \ ext_png \ ext_tiff \ ext_gsl \ ext_vc \ ext_libraw \ ext_giflib \ ext_fontconfig \ ext_freetype \ ext_poppler \ ext_python \ ext_sip \ ext_pyqt \ build_install_ext \ ext_yasm \ ext_nasm \ ext_libx265 \ ext_libde265 \ ext_libheif \ # Build kde_frameworks build_install_ext \ ext_extra_cmake_modules \ ext_kconfig \ ext_kwidgetsaddons \ ext_kcompletion \ ext_kcoreaddons \ ext_kguiaddons \ ext_ki18n \ ext_kitemmodels \ ext_kitemviews \ ext_kimageformats \ ext_kwindowsystem \ ext_quazip } #not tested set_krita_dirs() { if [[ -n ${1} ]]; then KIS_BUILD_DIR=${BUILDROOT}/b_${1} KIS_INSTALL_DIR=${BUILDROOT}/i_${1} KIS_SRC_DIR=${BUILDROOT}/src_${1} fi } # build_krita # run cmake krita build_krita () { export DYLD_FRAMEWORK_PATH=${FRAMEWORK_PATH} echo ${KIS_BUILD_DIR} echo ${KIS_INSTALL_DIR} log_cmd check_dir_path ${KIS_BUILD_DIR} cd ${KIS_BUILD_DIR} cmake ${KIS_SRC_DIR} \ -DFOUNDATION_BUILD=ON \ -DBoost_INCLUDE_DIR=${KIS_INSTALL_DIR}/include \ -DCMAKE_INSTALL_PREFIX=${KIS_INSTALL_DIR} \ -DDEFINE_NO_DEPRECATED=1 \ -DBUILD_TESTING=OFF \ - -DHIDE_SAFE_ASSERTS=OFF \ + -DHIDE_SAFE_ASSERTS=ON \ -DKDE_INSTALL_BUNDLEDIR=${KIS_INSTALL_DIR}/bin \ -DPYQT_SIP_DIR_OVERRIDE=${KIS_INSTALL_DIR}/share/sip/ \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_OSX_DEPLOYMENT_TARGET=10.12 \ -DPYTHON_INCLUDE_DIR=${KIS_INSTALL_DIR}/lib/Python.framework/Headers # copiling phase make -j${MAKE_THREADS} # compile integrations if test ${OSTYPE} == "darwin*"; then cd ${KIS_BUILD_DIR}/krita/integration/kritaquicklook make -j${MAKE_THREADS} fi } build_krita_tarball () { filename="$(basename ${1})" KIS_CUSTOM_BUILD="${BUILDROOT}/releases/${filename%.tar.gz}" print_msg "Tarball BUILDROOT is ${KIS_CUSTOM_BUILD}" filename_dir=$(dirname "${1}") cd "${filename_dir}" file_abspath="$(pwd)/${1##*/}" mkdir "${KIS_CUSTOM_BUILD}" 2> /dev/null cd "${KIS_CUSTOM_BUILD}" mkdir "src" "build" 2> /dev/null tar -xzf "${file_abspath}" --strip-components=1 --directory "src" if [[ $(print_if_error "Untar ${file_abspath} failed!") ]]; then exit fi KIS_BUILD_DIR="${KIS_CUSTOM_BUILD}/build" KIS_SRC_DIR="${KIS_CUSTOM_BUILD}/src" build_krita print_msg "Build done!" print_msg "to install run osxbuild.sh install ${KIS_BUILD_DIR}" } install_krita () { # custom install provided if [[ -n "${1}" ]]; then KIS_BUILD_DIR="${1}" fi print_msg "Install krita from ${KIS_BUILD_DIR}" log_cmd check_dir_path ${KIS_BUILD_DIR} cd ${KIS_BUILD_DIR} if [[ $(print_if_error "could not cd to ${KIS_BUILD_DIR}") ]]; then exit fi make install # compile integrations if test ${OSTYPE} == "darwin*"; then cd ${KIS_BUILD_DIR}/krita/integration/kritaquicklook make install fi } # Runs all fixes for path and packages. # Historically only fixed boost @rpath fix_boost_rpath () { # helpers to define function only once fixboost_find () { for FILE in "${@}"; do if [[ -n "$(otool -L $FILE | grep boost)" ]]; then log "Fixing -- $FILE" log_cmd install_name_tool -change libboost_system.dylib @rpath/libboost_system.dylib $FILE fi done } batch_fixboost() { xargs -P4 -I FILE bash -c 'fixboost_find "FILE"' } export -f fixboost_find export -f log export -f log_cmd print_msg "Fixing boost in... ${KIS_INSTALL_DIR}" # install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib $BUILDROOT/$KRITA_INSTALL/bin/krita.app/Contents/MacOS/gmic_krita_qt log_cmd install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita # echo "Added rpath ${KIS_INSTALL_DIR}/lib to krita bin" # install_name_tool -add_rpath ${BUILDROOT}/deps/lib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita log_cmd install_name_tool -change libboost_system.dylib @rpath/libboost_system.dylib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita find -L "${KIS_INSTALL_DIR}" -name '*so' -o -name '*dylib' | batch_fixboost log "Fixing boost done!" } print_usage () { printf "USAGE: osxbuild.sh [pkg|file]\n" printf "BUILDSTEPS:\t\t" printf "\n builddeps \t\t Run cmake step for 3rd party dependencies, optionally takes a [pkg] arg" printf "\n rebuilddeps \t\t Rerun make and make install step for 3rd party deps, optionally takes a [pkg] arg \t\t\t useful for cleaning install directory and quickly reinstall all deps." printf "\n fixboost \t\t Fixes broken boost \@rpath on OSX" printf "\n build \t\t\t Builds krita" printf "\n buildtarball \t\t\t Builds krita from provided [file] tarball" printf "\n install \t\t Installs krita. Optionally accepts a [build dir] as argument \t\t\t this will install krita from given directory" printf "\n buildinstall \t\t Build and Installs krita, running fixboost after installing" printf "\n" } if [[ ${#} -eq 0 ]]; then echo "ERROR: No option given!" print_usage exit 1 fi if [[ ${1} = "builddeps" ]]; then build_3rdparty "${@:2}" elif [[ ${1} = "rebuilddeps" ]]; then rebuild_3rdparty "${@:2}" elif [[ ${1} = "fixboost" ]]; then if [[ -d ${1} ]]; then KIS_BUILD_DIR="${1}" fi fix_boost_rpath elif [[ ${1} = "build" ]]; then build_krita ${2} elif [[ ${1} = "buildtarball" ]]; then # uncomment line to optionally change # install directory providing a third argument # This is not on by default as build success requires all # deps installed in the given dir beforehand. # KIS_INSTALL_DIR=${3} build_krita_tarball ${2} elif [[ ${1} = "install" ]]; then install_krita ${2} fix_boost_rpath elif [[ ${1} = "buildinstall" ]]; then build_krita ${2} install_krita ${2} fix_boost_rpath ${2} elif [[ ${1} = "test" ]]; then ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita else echo "Option ${1} not supported" print_usage exit 1 fi # after finishig sometimes it complains about missing matching quotes. diff --git a/plugins/color/colorspaceextensions/kis_desaturate_adjustment.cpp b/plugins/color/colorspaceextensions/kis_desaturate_adjustment.cpp index 7091e446c5..d870724128 100644 --- a/plugins/color/colorspaceextensions/kis_desaturate_adjustment.cpp +++ b/plugins/color/colorspaceextensions/kis_desaturate_adjustment.cpp @@ -1,193 +1,193 @@ /* * Copyright (c) 2013 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; version 2 * of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_desaturate_adjustment.h" #include #include #include #include #include #include #include #include #include #define SCALE_TO_FLOAT( v ) KoColorSpaceMaths< _channel_type_, float>::scaleToA( v ) #define SCALE_FROM_FLOAT( v ) KoColorSpaceMaths< float, _channel_type_>::scaleToA( v ) template class KisDesaturateAdjustment : public KoColorTransformation { typedef traits RGBTrait; typedef typename RGBTrait::Pixel RGBPixel; public: KisDesaturateAdjustment() { } public: void transform(const quint8 *srcU8, quint8 *dstU8, qint32 nPixels) const override { const RGBPixel* src = reinterpret_cast(srcU8); RGBPixel* dst = reinterpret_cast(dstU8); float r, g, b, gray; while (nPixels > 0) { r = SCALE_TO_FLOAT(src->red); g = SCALE_TO_FLOAT(src->green); b = SCALE_TO_FLOAT(src->blue); - // http://www.tannerhelland.com/3643/grayscale-image-algorithm-vb6/ + // https://www.tannerhelland.com/3643/grayscale-image-algorithm-vb6/ switch(m_type) { case 0: // lightness { gray = (qMax(qMax(r, g), b) + qMin(qMin(r, g), b)) / 2; break; } case 1: // luminosity BT 709 { gray = r * 0.2126 + g * 0.7152 + b * 0.0722; break; } case 2: // luminosity BT 601 { gray = r * 0.299 + g * 0.587 + b * 0.114; break; } case 3: // average { gray = (r + g + b) / 3; break; } case 4: // min { gray = qMin(qMin(r, g), b); break; } case 5: // min { gray = qMax(qMax(r, g), b); break; } default: gray = 0; } dst->red = SCALE_FROM_FLOAT(gray); dst->green = SCALE_FROM_FLOAT(gray); dst->blue = SCALE_FROM_FLOAT(gray); dst->alpha = src->alpha; --nPixels; ++src; ++dst; } } QList parameters() const override { QList list; list << "type"; return list; } int parameterId(const QString& name) const override { if (name == "type") { return 0; } return -1; } /** * name - "type": * 0: lightness * 1: luminosity * 2: average */ void setParameter(int id, const QVariant& parameter) override { switch(id) { case 0: m_type = parameter.toDouble(); break; default: ; } } private: int m_type; }; KisDesaturateAdjustmentFactory::KisDesaturateAdjustmentFactory() : KoColorTransformationFactory("desaturate_adjustment") { } QList< QPair< KoID, KoID > > KisDesaturateAdjustmentFactory::supportedModels() const { QList< QPair< KoID, KoID > > l; l.append(QPair< KoID, KoID >(RGBAColorModelID , Integer8BitsColorDepthID)); l.append(QPair< KoID, KoID >(RGBAColorModelID , Integer16BitsColorDepthID)); l.append(QPair< KoID, KoID >(RGBAColorModelID , Float16BitsColorDepthID)); l.append(QPair< KoID, KoID >(RGBAColorModelID , Float32BitsColorDepthID)); return l; } KoColorTransformation* KisDesaturateAdjustmentFactory::createTransformation(const KoColorSpace* colorSpace, QHash parameters) const { KoColorTransformation * adj; if (colorSpace->colorModelId() != RGBAColorModelID) { dbgKrita << "Unsupported color space " << colorSpace->id() << " in KisDesaturateAdjustmentFactory::createTransformation"; return 0; } if (colorSpace->colorDepthId() == Integer8BitsColorDepthID) { adj = new KisDesaturateAdjustment< quint8, KoBgrTraits < quint8 > >(); } else if (colorSpace->colorDepthId() == Integer16BitsColorDepthID) { adj = new KisDesaturateAdjustment< quint16, KoBgrTraits < quint16 > >(); } #ifdef HAVE_OPENEXR else if (colorSpace->colorDepthId() == Float16BitsColorDepthID) { adj = new KisDesaturateAdjustment< half, KoRgbTraits < half > >(); } #endif else if (colorSpace->colorDepthId() == Float32BitsColorDepthID) { adj = new KisDesaturateAdjustment< float, KoRgbTraits < float > >(); } else { dbgKrita << "Unsupported color space " << colorSpace->id() << " in KisDesaturateAdjustmentFactory::createTransformation"; return 0; } adj->setParameters(parameters); return adj; } diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_base.cpp b/plugins/dockers/advancedcolorselector/kis_color_selector_base.cpp index 8c2deba887..0394623b61 100644 --- a/plugins/dockers/advancedcolorselector/kis_color_selector_base.cpp +++ b/plugins/dockers/advancedcolorselector/kis_color_selector_base.cpp @@ -1,573 +1,580 @@ /* * Copyright (c) 2010 Adam Celarek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software */ #include "kis_color_selector_base.h" #include #include #include +#include #include #include #include #include #include #include #include #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "kis_canvas2.h" #include "kis_canvas_resource_provider.h" #include "kis_node.h" #include "KisViewManager.h" #include #include "kis_image.h" #include "kis_global.h" #include "kis_display_color_converter.h" #include class KisColorPreviewPopup : public QWidget { public: KisColorPreviewPopup(KisColorSelectorBase* parent) : QWidget(parent), m_parent(parent) { setWindowFlags(Qt::ToolTip | Qt::NoDropShadowWindowHint); setQColor(QColor(0,0,0)); m_baseColor = QColor(0,0,0,0); m_previousColor = QColor(0,0,0,0); m_lastUsedColor = QColor(0,0,0,0); } void show() { updatePosition(); QWidget::show(); } void updatePosition() { QPoint parentPos = m_parent->mapToGlobal(QPoint(0,0)); - QRect availRect = QApplication::desktop()->availableGeometry(this); + const QRect availRect = QApplication::desktop()->availableGeometry(this); QPoint targetPos; if ( parentPos.x() - 100 > availRect.x() ) { targetPos = QPoint(parentPos.x() - 100, parentPos.y()); } else if ( parentPos.x() + m_parent->width() + 100 < availRect.right()) { targetPos = m_parent->mapToGlobal(QPoint(m_parent->width(), 0)); } else if ( parentPos.y() - 100 > availRect.y() ) { targetPos = QPoint(parentPos.x(), parentPos.y() - 100); } else { targetPos = QPoint(parentPos.x(), parentPos.y() + m_parent->height()); } setGeometry(targetPos.x(), targetPos.y(), 100, 150); setAttribute(Qt::WA_TranslucentBackground); } void setQColor(const QColor& color) { m_color = color; update(); } void setPreviousColor() { m_previousColor = m_baseColor; } void setBaseColor(const QColor& color) { m_baseColor = color; update(); } void setLastUsedColor(const QColor& color) { m_lastUsedColor = color; update(); } protected: void paintEvent(QPaintEvent *e) override { Q_UNUSED(e); QPainter p(this); p.fillRect(0, 0, width(), width(), m_color); p.fillRect(50, width(), width(), height(), m_previousColor); p.fillRect(0, width(), 50, height(), m_lastUsedColor); } void enterEvent(QEvent *e) override { QWidget::enterEvent(e); m_parent->tryHideAllPopups(); } void leaveEvent(QEvent *e) override { QWidget::leaveEvent(e); m_parent->tryHideAllPopups(); } private: KisColorSelectorBase* m_parent; QColor m_color; QColor m_baseColor; QColor m_previousColor; QColor m_lastUsedColor; }; KisColorSelectorBase::KisColorSelectorBase(QWidget *parent) : QWidget(parent), m_canvas(0), m_popup(0), m_parent(0), m_colorUpdateAllowed(true), m_colorUpdateSelf(false), m_hideTimer(new QTimer(this)), m_popupOnMouseOver(false), m_popupOnMouseClick(true), m_colorSpace(0), m_isPopup(false), m_hideOnMouseClick(false), m_colorPreviewPopup(new KisColorPreviewPopup(this)) { m_hideTimer->setInterval(0); m_hideTimer->setSingleShot(true); connect(m_hideTimer, SIGNAL(timeout()), this, SLOT(hidePopup())); using namespace std::placeholders; // For _1 placeholder auto function = std::bind(&KisColorSelectorBase::slotUpdateColorAndPreview, this, _1); m_updateColorCompressor.reset(new ColorCompressorType(20 /* ms */, function)); } KisColorSelectorBase::~KisColorSelectorBase() { delete m_popup; delete m_colorPreviewPopup; } void KisColorSelectorBase::setPopupBehaviour(bool onMouseOver, bool onMouseClick) { m_popupOnMouseClick = onMouseClick; m_popupOnMouseOver = onMouseOver; if(onMouseClick) { m_popupOnMouseOver = false; } if(m_popupOnMouseOver) { setMouseTracking(true); } } void KisColorSelectorBase::setColorSpace(const KoColorSpace *colorSpace) { m_colorSpace = colorSpace; } void KisColorSelectorBase::setCanvas(KisCanvas2 *canvas) { if (m_canvas) { m_canvas->disconnectCanvasObserver(this); } m_canvas = canvas; if (m_canvas) { connect(m_canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), SLOT(canvasResourceChanged(int,QVariant)), Qt::UniqueConnection); connect(m_canvas->displayColorConverter(), SIGNAL(displayConfigurationChanged()), SLOT(reset()), Qt::UniqueConnection); connect(canvas->imageView()->resourceProvider(), SIGNAL(sigFGColorUsed(KoColor)), this, SLOT(updateLastUsedColorPreview(KoColor)), Qt::UniqueConnection); if (m_canvas->viewManager() && m_canvas->viewManager()->canvasResourceProvider()) { setColor(Acs::currentColor(m_canvas->viewManager()->canvasResourceProvider(), Acs::Foreground)); } } if (m_popup) { m_popup->setCanvas(canvas); } reset(); } void KisColorSelectorBase::unsetCanvas() { if (m_popup) { m_popup->unsetCanvas(); } m_canvas = 0; } void KisColorSelectorBase::mousePressEvent(QMouseEvent* event) { event->accept(); if(!m_isPopup && m_popupOnMouseClick && event->button() == Qt::MidButton) { lazyCreatePopup(); int x = event->globalX(); int y = event->globalY(); int popupsize = m_popup->width(); x-=popupsize/2; y-=popupsize/2; - QRect availRect = QApplication::desktop()->availableGeometry(this); + const QRect availRect = QApplication::desktop()->availableGeometry(this); + if(xwidth()>availRect.x()+availRect.width()) x = availRect.x()+availRect.width()-m_popup->width(); if(y+m_popup->height()>availRect.y()+availRect.height()) y = availRect.y()+availRect.height()-m_popup->height(); m_colorUpdateSelf=false; m_popup->move(x, y); m_popup->setHidingTime(200); showPopup(DontMove); } else if (m_isPopup && event->button() == Qt::MidButton) { if (m_colorPreviewPopup) { m_colorPreviewPopup->hide(); } hide(); } else { m_colorUpdateSelf=true; showColorPreview(); event->ignore(); } } void KisColorSelectorBase::mouseReleaseEvent(QMouseEvent *e) { Q_UNUSED(e); if (e->button() == Qt::MidButton) { e->accept(); } else if (m_isPopup && (m_hideOnMouseClick && !m_popupOnMouseOver) && !m_hideTimer->isActive()) { if (m_colorPreviewPopup) { m_colorPreviewPopup->hide(); } hide(); } } void KisColorSelectorBase::enterEvent(QEvent *e) { if (m_popup && m_popup->isVisible()) { m_popup->m_hideTimer->stop(); } if (m_isPopup && m_hideTimer->isActive()) { m_hideTimer->stop(); } // do not show the popup when boxed in // the configuration dialog (m_canvas == 0) if (m_canvas && !m_isPopup && m_popupOnMouseOver && (!m_popup || m_popup->isHidden())) { lazyCreatePopup(); const QRect availRect = QApplication::desktop()->availableGeometry(this); QPoint proposedTopLeft = rect().center() - m_popup->rect().center(); proposedTopLeft = mapToGlobal(proposedTopLeft); QRect popupRect = QRect(proposedTopLeft, m_popup->size()); popupRect = kisEnsureInRect(popupRect, availRect); m_popup->setGeometry(popupRect); m_popup->setHidingTime(200); showPopup(DontMove); } QWidget::enterEvent(e); } void KisColorSelectorBase::leaveEvent(QEvent *e) { tryHideAllPopups(); QWidget::leaveEvent(e); } void KisColorSelectorBase::keyPressEvent(QKeyEvent *) { if (m_isPopup) { hidePopup(); } } void KisColorSelectorBase::dragEnterEvent(QDragEnterEvent *e) { if(e->mimeData()->hasColor()) e->acceptProposedAction(); if(e->mimeData()->hasText() && QColor(e->mimeData()->text()).isValid()) e->acceptProposedAction(); } void KisColorSelectorBase::dropEvent(QDropEvent *e) { QColor color; if(e->mimeData()->hasColor()) { color = qvariant_cast(e->mimeData()->colorData()); } else if(e->mimeData()->hasText()) { color.setNamedColor(e->mimeData()->text()); if(!color.isValid()) return; } KoColor kocolor(color , KoColorSpaceRegistry::instance()->rgb8()); updateColor(kocolor, Acs::Foreground, true); } void KisColorSelectorBase::updateColor(const KoColor &color, Acs::ColorRole role, bool needsExplicitColorReset) { commitColor(color, role); if (needsExplicitColorReset) { setColor(color); } } void KisColorSelectorBase::requestUpdateColorAndPreview(const KoColor &color, Acs::ColorRole role) { m_updateColorCompressor->start(qMakePair(color, role)); } void KisColorSelectorBase::slotUpdateColorAndPreview(QPair color) { updateColorPreview(color.first); updateColor(color.first, color.second, false); } void KisColorSelectorBase::setColor(const KoColor& color) { Q_UNUSED(color); } void KisColorSelectorBase::setHidingTime(int time) { KIS_ASSERT_RECOVER_NOOP(m_isPopup); m_hideTimer->setInterval(time); } void KisColorSelectorBase::lazyCreatePopup() { if (!m_popup) { m_popup = createPopup(); Q_ASSERT(m_popup); m_popup->setParent(this); // Setting Qt::BypassWindowManagerHint will prevent // the WM from showing another taskbar entry, // but will require that we handle window activation manually m_popup->setWindowFlags(Qt::FramelessWindowHint | - Qt::Window | + Qt::Popup | Qt::NoDropShadowWindowHint | Qt::BypassWindowManagerHint); m_popup->m_parent = this; m_popup->m_isPopup = true; } m_popup->setCanvas(m_canvas); m_popup->updateSettings(); } void KisColorSelectorBase::showPopup(Move move) { // This slot may be called by some action, // so we need to be able to handle it lazyCreatePopup(); QPoint cursorPos = QCursor::pos(); + QScreen *activeScreen = 0; +#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) + activeScreen = QGuiApplication::screenAt(cursorPos); +#endif + const QRect availRect = (activeScreen)? activeScreen->availableGeometry() : QApplication::desktop()->availableGeometry(this); if (move == MoveToMousePosition) { m_popup->move(QPoint(cursorPos.x()-m_popup->width()/2, cursorPos.y()-m_popup->height()/2)); QRect rc = m_popup->geometry(); - if (rc.x() < 0) rc.setX(0); - if (rc.y() < 0) rc.setY(0); + if (rc.x() < availRect.x()) rc.setX(availRect.x()); + if (rc.y() < availRect.y()) rc.setY(availRect.y()); m_popup->setGeometry(rc); } if (m_colorPreviewPopup) { m_colorPreviewPopup->hide(); } m_popup->show(); m_popup->m_colorPreviewPopup->show(); } void KisColorSelectorBase::hidePopup() { KIS_ASSERT_RECOVER_RETURN(m_isPopup); m_colorPreviewPopup->hide(); hide(); } void KisColorSelectorBase::commitColor(const KoColor& color, Acs::ColorRole role) { if (!m_canvas) return; m_colorUpdateAllowed=false; if (role == Acs::Foreground) m_canvas->resourceManager()->setForegroundColor(color); else m_canvas->resourceManager()->setBackgroundColor(color); m_colorUpdateAllowed=true; } void KisColorSelectorBase::showColorPreview() { if(m_colorPreviewPopup->isHidden()) { m_colorPreviewPopup->show(); } } void KisColorSelectorBase::updateColorPreview(const KoColor &color) { m_colorPreviewPopup->setQColor(converter()->toQColor(color)); } void KisColorSelectorBase::canvasResourceChanged(int key, const QVariant &v) { if (key == KoCanvasResourceProvider::ForegroundColor || key == KoCanvasResourceProvider::BackgroundColor) { KoColor realColor(v.value()); updateColorPreview(realColor); if (m_colorUpdateAllowed && !m_colorUpdateSelf) { setColor(realColor); } } } const KoColorSpace* KisColorSelectorBase::colorSpace() const { return converter()->paintingColorSpace(); } void KisColorSelectorBase::updateSettings() { if(m_popup) { m_popup->updateSettings(); } KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); int zoomSelectorOptions = (int) cfg.readEntry("zoomSelectorOptions", 0) ; if (zoomSelectorOptions == 0) { setPopupBehaviour(false, true); // middle mouse button click will open zoom selector } else if (zoomSelectorOptions == 1) { setPopupBehaviour(true, false); // move over will open the zoom selector } else { setPopupBehaviour(false, false); // do not show zoom selector } if(m_isPopup) { m_hideOnMouseClick = cfg.readEntry("hidePopupOnClickCheck", false); const int zoomSize = cfg.readEntry("zoomSize", 280); resize(zoomSize, zoomSize); } reset(); } void KisColorSelectorBase::reset() { update(); } void KisColorSelectorBase::updateBaseColorPreview(const KoColor &color) { m_colorPreviewPopup->setBaseColor(converter()->toQColor(color)); } void KisColorSelectorBase::updatePreviousColorPreview() { m_colorPreviewPopup->setPreviousColor(); } void KisColorSelectorBase::updateLastUsedColorPreview(const KoColor &color) { m_colorPreviewPopup->setLastUsedColor(converter()->toQColor(color)); } KisDisplayColorConverter* KisColorSelectorBase::converter() const { return m_canvas ? m_canvas->displayColorConverter() : KisDisplayColorConverter::dumbConverterInstance(); } void KisColorSelectorBase::tryHideAllPopups() { if (m_colorPreviewPopup->isVisible()) { m_colorUpdateSelf=false; //this is for allowing advanced selector to listen to outside color-change events. m_colorPreviewPopup->hide(); } if (m_popup && m_popup->isVisible()) { m_popup->m_hideTimer->start(); } if (m_isPopup && !m_hideTimer->isActive()) { m_hideTimer->start(); } } void KisColorSelectorBase::mouseMoveEvent(QMouseEvent *event) { event->accept(); } void KisColorSelectorBase::changeEvent(QEvent *event) { // hide the popup when another window becomes active, e.g. due to alt+tab if(m_isPopup && event->type() == QEvent::ActivationChange && !isActiveWindow()) { hidePopup(); } QWidget::changeEvent(event); } void KisColorSelectorBase::showEvent(QShowEvent *event) { QWidget::showEvent(event); // manual activation required due to Qt::BypassWindowManagerHint if(m_isPopup) { activateWindow(); } } diff --git a/plugins/dockers/animation/kis_animation_curves_view.cpp b/plugins/dockers/animation/kis_animation_curves_view.cpp index b06308cc01..1f6d8e97f5 100644 --- a/plugins/dockers/animation/kis_animation_curves_view.cpp +++ b/plugins/dockers/animation/kis_animation_curves_view.cpp @@ -1,742 +1,738 @@ /* * Copyright (c) 2016 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * 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_animation_curves_view.h" #include #include #include #include #include #include #include "kis_animation_curves_model.h" #include "timeline_ruler_header.h" #include "kis_animation_curves_value_ruler.h" #include "kis_animation_curves_keyframe_delegate.h" #include "kis_scalar_keyframe_channel.h" #include "kis_zoom_button.h" #include "kis_custom_modifiers_catcher.h" const int VERTICAL_PADDING = 30; struct KisAnimationCurvesView::Private { Private() - : model(0) - , isDraggingKeyframe(false) - , isAdjustingHandle(false) - , panning(false) {} - KisAnimationCurvesModel *model; - TimelineRulerHeader *horizontalHeader; - KisAnimationCurvesValueRuler *verticalHeader; - KisAnimationCurvesKeyframeDelegate *itemDelegate; - KisZoomButton *horizontalZoomButton; - KisZoomButton *verticalZoomButton; - KisCustomModifiersCatcher *modifiersCatcher; - - bool isDraggingKeyframe; - bool isAdjustingHandle; - int adjustedHandle; // 0 = left, 1 = right + KisAnimationCurvesModel *model {0}; + TimelineRulerHeader *horizontalHeader {0}; + KisAnimationCurvesValueRuler *verticalHeader {0}; + KisAnimationCurvesKeyframeDelegate *itemDelegate {0}; + KisZoomButton *horizontalZoomButton {0}; + KisZoomButton *verticalZoomButton {0}; + KisCustomModifiersCatcher *modifiersCatcher {0}; + + bool isDraggingKeyframe {false}; + bool isAdjustingHandle {false}; + int adjustedHandle {0}; // 0 = left, 1 = right QPoint dragStart; QPoint dragOffset; - int horizontalZoomStillPointIndex; - int horizontalZoomStillPointOriginalOffset; - qreal verticalZoomStillPoint; - qreal verticalZoomStillPointOriginalOffset; + int horizontalZoomStillPointIndex {0}; + int horizontalZoomStillPointOriginalOffset {0}; + qreal verticalZoomStillPoint {0.0}; + qreal verticalZoomStillPointOriginalOffset {0.0}; - bool panning; + bool panning {false}; QPoint panStartOffset; }; KisAnimationCurvesView::KisAnimationCurvesView(QWidget *parent) : QAbstractItemView(parent) , m_d(new Private()) { m_d->horizontalHeader = new TimelineRulerHeader(this); m_d->verticalHeader = new KisAnimationCurvesValueRuler(this); m_d->itemDelegate = new KisAnimationCurvesKeyframeDelegate(m_d->horizontalHeader, m_d->verticalHeader, this); m_d->modifiersCatcher = new KisCustomModifiersCatcher(this); m_d->modifiersCatcher->addModifier("pan-zoom", Qt::Key_Space); setSelectionMode(QAbstractItemView::ExtendedSelection); setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(this); if (scroller){ connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State))); } } KisAnimationCurvesView::~KisAnimationCurvesView() {} void KisAnimationCurvesView::setModel(QAbstractItemModel *model) { m_d->model = dynamic_cast(model); QAbstractItemView::setModel(model); m_d->horizontalHeader->setModel(model); connect(model, &QAbstractItemModel::rowsInserted, this, &KisAnimationCurvesView::slotRowsChanged); connect(model, &QAbstractItemModel::rowsRemoved, this, &KisAnimationCurvesView::slotRowsChanged); connect(model, &QAbstractItemModel::dataChanged, this, &KisAnimationCurvesView::slotDataChanged); connect(model, &QAbstractItemModel::headerDataChanged, this, &KisAnimationCurvesView::slotHeaderDataChanged); } void KisAnimationCurvesView::setZoomButtons(KisZoomButton *horizontal, KisZoomButton *vertical) { m_d->horizontalZoomButton = horizontal; m_d->verticalZoomButton = vertical; connect(horizontal, &KisZoomButton::zoomStarted, this, &KisAnimationCurvesView::slotHorizontalZoomStarted); connect(horizontal, &KisZoomButton::zoomLevelChanged, this, &KisAnimationCurvesView::slotHorizontalZoomLevelChanged); connect(vertical, &KisZoomButton::zoomStarted, this, &KisAnimationCurvesView::slotVerticalZoomStarted); connect(vertical, &KisZoomButton::zoomLevelChanged, this, &KisAnimationCurvesView::slotVerticalZoomLevelChanged); } QRect KisAnimationCurvesView::visualRect(const QModelIndex &index) const { return m_d->itemDelegate->itemRect(index); } void KisAnimationCurvesView::scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint hint) { // TODO Q_UNUSED(index); Q_UNUSED(hint); } QModelIndex KisAnimationCurvesView::indexAt(const QPoint &point) const { if (!model()) return QModelIndex(); int time = m_d->horizontalHeader->logicalIndexAt(point.x()); int rows = model()->rowCount(); for (int row=0; row < rows; row++) { QModelIndex index = model()->index(row, time); if (index.data(KisTimeBasedItemModel::SpecialKeyframeExists).toBool()) { QRect nodePos = m_d->itemDelegate->itemRect(index); if (nodePos.contains(point)) { return index; } } } return QModelIndex(); } void KisAnimationCurvesView::paintEvent(QPaintEvent *e) { QPainter painter(viewport()); QRect r = e->rect(); r.translate(dirtyRegionOffset()); int firstFrame = m_d->horizontalHeader->logicalIndexAt(r.left()); int lastFrame = m_d->horizontalHeader->logicalIndexAt(r.right()); if (lastFrame == -1) lastFrame = model()->columnCount(); paintCurves(painter, firstFrame, lastFrame); paintKeyframes(painter, firstFrame, lastFrame); } void KisAnimationCurvesView::paintCurves(QPainter &painter, int firstFrame, int lastFrame) { int channels = model()->rowCount(); for (int channel = 0; channel < channels; channel++) { QModelIndex index0 = model()->index(channel, 0); if (!isIndexHidden(index0)) { QColor color = index0.data(KisAnimationCurvesModel::CurveColorRole).value(); painter.setPen(QPen(color, 1)); paintCurve(channel, firstFrame, lastFrame, painter); } } } void KisAnimationCurvesView::paintCurve(int channel, int firstFrame, int lastFrame, QPainter &painter) { int selectionOffset = m_d->isDraggingKeyframe ? (m_d->dragOffset.x() / m_d->horizontalHeader->defaultSectionSize()) : 0; QModelIndex index = findNextKeyframeIndex(channel, firstFrame+1, selectionOffset, true); if (!index.isValid()) { index = findNextKeyframeIndex(channel, firstFrame, selectionOffset, false); } if (!index.isValid()) return; QPointF previousKeyPos = m_d->itemDelegate->nodeCenter(index, selectionModel()->isSelected(index)); QPointF rightTangent = m_d->itemDelegate->rightHandle(index, index == currentIndex()); while(index.column() <= lastFrame) { int interpolationMode = index.data(KisAnimationCurvesModel::InterpolationModeRole).toInt(); int time = (m_d->isDraggingKeyframe && selectionModel()->isSelected(index)) ? index.column() + selectionOffset : index.column(); index = findNextKeyframeIndex(channel, time, selectionOffset, false); if (!index.isValid()) return; bool active = (index == currentIndex()); QPointF nextKeyPos = m_d->itemDelegate->nodeCenter(index, selectionModel()->isSelected(index)); QPointF leftTangent = m_d->itemDelegate->leftHandle(index, active); if (interpolationMode == KisKeyframe::Constant) { painter.drawLine(previousKeyPos, QPointF(nextKeyPos.x(), previousKeyPos.y())); } else if (interpolationMode == KisKeyframe::Linear) { painter.drawLine(previousKeyPos, nextKeyPos); } else { paintCurveSegment(painter, previousKeyPos, rightTangent, leftTangent, nextKeyPos); } previousKeyPos = nextKeyPos; rightTangent = m_d->itemDelegate->rightHandle(index, active); } } void KisAnimationCurvesView::paintCurveSegment(QPainter &painter, QPointF pos1, QPointF rightTangent, QPointF leftTangent, QPointF pos2) { const int steps = 16; QPointF previousPos; for (int step = 0; step <= steps; step++) { qreal t = 1.0 * step / steps; QPointF nextPos = KisScalarKeyframeChannel::interpolate(pos1, rightTangent, leftTangent, pos2, t); if (step > 0) { painter.drawLine(previousPos, nextPos); } previousPos = nextPos; } } void KisAnimationCurvesView::paintKeyframes(QPainter &painter, int firstFrame, int lastFrame) { int channels = model()->rowCount(); for (int channel = 0; channel < channels; channel++) { for (int time=firstFrame; time <= lastFrame; time++) { QModelIndex index = model()->index(channel, time); bool keyframeExists = model()->data(index, KisAnimationCurvesModel::SpecialKeyframeExists).toReal(); if (keyframeExists && !isIndexHidden(index)) { QStyleOptionViewItem opt; if (selectionModel()->isSelected(index)) { opt.state |= QStyle::State_Selected; } if (index == selectionModel()->currentIndex()) { opt.state |= QStyle::State_HasFocus; } m_d->itemDelegate->paint(&painter, opt, index); } } } } QModelIndex KisAnimationCurvesView::findNextKeyframeIndex(int channel, int time, int selectionOffset, bool backward) { KisAnimationCurvesModel::ItemDataRole role = backward ? KisAnimationCurvesModel::PreviousKeyframeTime : KisAnimationCurvesModel::NextKeyframeTime; QModelIndex currentIndex = model()->index(channel, time); if (!selectionOffset) { QVariant next = currentIndex.data(role); return (next.isValid()) ? model()->index(channel, next.toInt()) : QModelIndex(); } else { // Find the next unselected index QModelIndex nextIndex = currentIndex; do { QVariant next = nextIndex.data(role); nextIndex = (next.isValid()) ? model()->index(channel, next.toInt()) : QModelIndex(); } while(nextIndex.isValid() && selectionModel()->isSelected(nextIndex)); // Find the next selected index, accounting for D&D offset QModelIndex draggedIndex = model()->index(channel, qMax(0, time - selectionOffset)); do { QVariant next = draggedIndex.data(role); draggedIndex = (next.isValid()) ? model()->index(channel, next.toInt()) : QModelIndex(); } while(draggedIndex.isValid() && !selectionModel()->isSelected(draggedIndex)); // Choose the earlier of the two if (draggedIndex.isValid() && nextIndex.isValid()) { if (draggedIndex.column() + selectionOffset <= nextIndex.column()) { return draggedIndex; } else { return nextIndex; } } else if (draggedIndex.isValid()) { return draggedIndex; } else { return nextIndex; } } } void KisAnimationCurvesView::findExtremes(qreal *minimum, qreal *maximum) { if (!model()) return; qreal min = qInf(); qreal max = -qInf(); int rows = model()->rowCount(); for (int row = 0; row < rows; row++) { QModelIndex index = model()->index(row, 0); if (isIndexHidden(index)) continue; QVariant nextTime; do { qreal value = index.data(KisAnimationCurvesModel::ScalarValueRole).toReal(); if (value < min) min = value; if (value > max) max = value; nextTime = index.data(KisAnimationCurvesModel::NextKeyframeTime); if (nextTime.isValid()) index = model()->index(row, nextTime.toInt()); } while (nextTime.isValid()); } if (qIsFinite(min)) *minimum = min; if (qIsFinite(max)) *maximum = max; } void KisAnimationCurvesView::updateVerticalRange() { if (!model()) return; qreal minimum = 0; qreal maximum = 0; findExtremes(&minimum, &maximum); int viewMin = maximum * m_d->verticalHeader->scaleFactor(); int viewMax = minimum * m_d->verticalHeader->scaleFactor(); viewMin -= VERTICAL_PADDING; viewMax += VERTICAL_PADDING; verticalScrollBar()->setRange(viewMin, viewMax - viewport()->height()); } void KisAnimationCurvesView::startPan(QPoint mousePos) { m_d->dragStart = mousePos; m_d->panStartOffset = QPoint(horizontalOffset(), verticalOffset()); m_d->panning = true; } QModelIndex KisAnimationCurvesView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) { // TODO Q_UNUSED(cursorAction); Q_UNUSED(modifiers); return QModelIndex(); } int KisAnimationCurvesView::horizontalOffset() const { return m_d->horizontalHeader->offset(); } int KisAnimationCurvesView::verticalOffset() const { return m_d->verticalHeader->offset(); } bool KisAnimationCurvesView::isIndexHidden(const QModelIndex &index) const { return !index.data(KisAnimationCurvesModel::CurveVisibleRole).toBool(); } void KisAnimationCurvesView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) { int timeFrom = m_d->horizontalHeader->logicalIndexAt(rect.left()); int timeTo = m_d->horizontalHeader->logicalIndexAt(rect.right()); QItemSelection selection; int rows = model()->rowCount(); for (int row=0; row < rows; row++) { for (int time = timeFrom; time <= timeTo; time++) { QModelIndex index = model()->index(row, time); if (index.data(KisTimeBasedItemModel::SpecialKeyframeExists).toBool()) { QRect itemRect = m_d->itemDelegate->itemRect(index); if (itemRect.intersects(rect)) { selection.select(index, index); } } } } selectionModel()->select(selection, command); } QRegion KisAnimationCurvesView::visualRegionForSelection(const QItemSelection &selection) const { QRegion region; Q_FOREACH(QModelIndex index, selection.indexes()) { region += m_d->itemDelegate->visualRect(index); } return region; } void KisAnimationCurvesView::mousePressEvent(QMouseEvent *e) { if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) { if (e->button() == Qt::LeftButton) { startPan(e->pos()); } else { qreal horizontalStaticPoint = m_d->horizontalHeader->logicalIndexAt(e->pos().x()); qreal verticalStaticPoint = m_d->verticalHeader->mapViewToValue(e->pos().y()); m_d->horizontalZoomButton->beginZoom(QPoint(e->pos().x(), 0), horizontalStaticPoint); m_d->verticalZoomButton->beginZoom(QPoint(0, e->pos().y()), verticalStaticPoint); } } else if (e->button() == Qt::LeftButton) { m_d->dragStart = e->pos(); Q_FOREACH(QModelIndex index, selectedIndexes()) { QPointF center = m_d->itemDelegate->nodeCenter(index, false); bool hasLeftHandle = m_d->itemDelegate->hasHandle(index, 0); bool hasRightHandle = m_d->itemDelegate->hasHandle(index, 1); QPointF leftHandle = center + m_d->itemDelegate->leftHandle(index, false); QPointF rightHandle = center + m_d->itemDelegate->rightHandle(index, false); if (hasLeftHandle && (e->localPos() - leftHandle).manhattanLength() < 8) { m_d->isAdjustingHandle = true; m_d->adjustedHandle = 0; setCurrentIndex(index); return; } else if (hasRightHandle && (e->localPos() - rightHandle).manhattanLength() < 8) { m_d->isAdjustingHandle = true; m_d->adjustedHandle = 1; setCurrentIndex(index); return; } } } QAbstractItemView::mousePressEvent(e); } void KisAnimationCurvesView::mouseMoveEvent(QMouseEvent *e) { if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) { if (e->buttons() & Qt::LeftButton) { if (!m_d->panning) startPan(e->pos()); QPoint diff = e->pos() - m_d->dragStart; QPoint newOffset = m_d->panStartOffset - diff; horizontalScrollBar()->setValue(newOffset.x()); verticalScrollBar()->setValue(newOffset.y()); m_d->verticalHeader->setOffset(newOffset.y()); viewport()->update(); } else { m_d->horizontalZoomButton->continueZoom(QPoint(e->pos().x(), 0)); m_d->verticalZoomButton->continueZoom(QPoint(0, e->pos().y())); } } else if (e->buttons() & Qt::LeftButton) { m_d->dragOffset = e->pos() - m_d->dragStart; if (m_d->isAdjustingHandle) { m_d->itemDelegate->setHandleAdjustment(m_d->dragOffset, m_d->adjustedHandle); viewport()->update(); return; } else if (m_d->isDraggingKeyframe) { m_d->itemDelegate->setSelectedItemVisualOffset(m_d->dragOffset); viewport()->update(); return; } else if (selectionModel()->hasSelection()) { if ((e->pos() - m_d->dragStart).manhattanLength() > QApplication::startDragDistance()) { m_d->isDraggingKeyframe = true; } } } QAbstractItemView::mouseMoveEvent(e); } void KisAnimationCurvesView::mouseReleaseEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { m_d->panning = false; if (m_d->isDraggingKeyframe) { QModelIndexList indexes = selectedIndexes(); int timeOffset = m_d->dragOffset.x() / m_d->horizontalHeader->defaultSectionSize(); qreal valueOffset = m_d->dragOffset.y() / m_d->verticalHeader->scaleFactor(); KisAnimationCurvesModel *curvesModel = dynamic_cast(model()); curvesModel->adjustKeyframes(indexes, timeOffset, valueOffset); m_d->isDraggingKeyframe = false; m_d->itemDelegate->setSelectedItemVisualOffset(QPointF()); viewport()->update(); } else if (m_d->isAdjustingHandle) { QModelIndex index = currentIndex(); int mode = index.data(KisAnimationCurvesModel::TangentsModeRole).toInt(); m_d->model->beginCommand(kundo2_i18n("Adjust tangent")); if (mode == KisKeyframe::Smooth) { QPointF leftHandle = m_d->itemDelegate->leftHandle(index, true); QPointF rightHandle = m_d->itemDelegate->rightHandle(index, true); QPointF leftTangent = m_d->itemDelegate->unscaledTangent(leftHandle); QPointF rightTangent = m_d->itemDelegate->unscaledTangent(rightHandle); model()->setData(index, leftTangent, KisAnimationCurvesModel::LeftTangentRole); model()->setData(index, rightTangent, KisAnimationCurvesModel::RightTangentRole); } else { if (m_d->adjustedHandle == 0) { QPointF leftHandle = m_d->itemDelegate->leftHandle(index, true); model()->setData(index, m_d->itemDelegate->unscaledTangent(leftHandle), KisAnimationCurvesModel::LeftTangentRole); } else { QPointF rightHandle = m_d->itemDelegate->rightHandle(index, true); model()->setData(index, m_d->itemDelegate->unscaledTangent(rightHandle), KisAnimationCurvesModel::RightTangentRole); } } m_d->model->endCommand(); m_d->isAdjustingHandle = false; m_d->itemDelegate->setHandleAdjustment(QPointF(), m_d->adjustedHandle); } } QAbstractItemView::mouseReleaseEvent(e); } void KisAnimationCurvesView::scrollContentsBy(int dx, int dy) { m_d->horizontalHeader->setOffset(horizontalScrollBar()->value()); m_d->verticalHeader->setOffset(verticalScrollBar()->value()); scrollDirtyRegion(dx, dy); viewport()->scroll(dx, dy); } void KisAnimationCurvesView::updateGeometries() { int topMargin = qMax(m_d->horizontalHeader->minimumHeight(), m_d->horizontalHeader->sizeHint().height()); int leftMargin = m_d->verticalHeader->sizeHint().width(); setViewportMargins(leftMargin, topMargin, 0, 0); QRect viewRect = viewport()->geometry(); m_d->horizontalHeader->setGeometry(leftMargin, 0, viewRect.width(), topMargin); m_d->verticalHeader->setGeometry(0, topMargin, leftMargin, viewRect.height()); horizontalScrollBar()->setRange(0, m_d->horizontalHeader->length() - viewport()->width()); updateVerticalRange(); QAbstractItemView::updateGeometries(); } void KisAnimationCurvesView::slotRowsChanged(const QModelIndex &parentIndex, int first, int last) { Q_UNUSED(parentIndex); Q_UNUSED(first); Q_UNUSED(last); updateVerticalRange(); viewport()->update(); } void KisAnimationCurvesView::slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { Q_UNUSED(topLeft); Q_UNUSED(bottomRight); updateVerticalRange(); viewport()->update(); } void KisAnimationCurvesView::slotHeaderDataChanged(Qt::Orientation orientation, int first, int last) { Q_UNUSED(orientation); Q_UNUSED(first); Q_UNUSED(last); viewport()->update(); } void KisAnimationCurvesView::slotHorizontalZoomStarted(qreal staticPoint) { m_d->horizontalZoomStillPointIndex = qIsNaN(staticPoint) ? currentIndex().column() : staticPoint; const int w = m_d->horizontalHeader->defaultSectionSize(); m_d->horizontalZoomStillPointOriginalOffset = w * m_d->horizontalZoomStillPointIndex - horizontalScrollBar()->value(); } void KisAnimationCurvesView::slotHorizontalZoomLevelChanged(qreal zoomLevel) { if (m_d->horizontalHeader->setZoom(zoomLevel)) { const int w = m_d->horizontalHeader->defaultSectionSize(); horizontalScrollBar()->setValue(w * m_d->horizontalZoomStillPointIndex - m_d->horizontalZoomStillPointOriginalOffset); viewport()->update(); } } void KisAnimationCurvesView::slotVerticalZoomStarted(qreal staticPoint) { m_d->verticalZoomStillPoint = qIsNaN(staticPoint) ? 0 : staticPoint; const float scale = m_d->verticalHeader->scaleFactor(); m_d->verticalZoomStillPointOriginalOffset = scale * m_d->verticalZoomStillPoint - m_d->verticalHeader->offset(); } void KisAnimationCurvesView::slotVerticalZoomLevelChanged(qreal zoomLevel) { if (!qFuzzyCompare((float)zoomLevel, m_d->verticalHeader->scaleFactor())) { m_d->verticalHeader->setScale(zoomLevel); m_d->verticalHeader->setOffset(-zoomLevel * m_d->verticalZoomStillPoint - m_d->verticalZoomStillPointOriginalOffset); verticalScrollBar()->setValue(m_d->verticalHeader->offset()); viewport()->update(); } } void KisAnimationCurvesView::applyConstantMode() { m_d->model->beginCommand(kundo2_i18n("Set interpolation mode")); Q_FOREACH(QModelIndex index, selectedIndexes()) { m_d->model->setData(index, KisKeyframe::Constant, KisAnimationCurvesModel::InterpolationModeRole); } m_d->model->endCommand(); } void KisAnimationCurvesView::applyLinearMode() { m_d->model->beginCommand(kundo2_i18n("Set interpolation mode")); Q_FOREACH(QModelIndex index, selectedIndexes()) { m_d->model->setData(index, KisKeyframe::Linear, KisAnimationCurvesModel::InterpolationModeRole); } m_d->model->endCommand(); } void KisAnimationCurvesView::applyBezierMode() { m_d->model->beginCommand(kundo2_i18n("Set interpolation mode")); Q_FOREACH(QModelIndex index, selectedIndexes()) { m_d->model->setData(index, KisKeyframe::Bezier, KisAnimationCurvesModel::InterpolationModeRole); } m_d->model->endCommand(); } void KisAnimationCurvesView::applySmoothMode() { m_d->model->beginCommand(kundo2_i18n("Set interpolation mode")); Q_FOREACH(QModelIndex index, selectedIndexes()) { QVector2D leftVisualTangent(m_d->itemDelegate->leftHandle(index, false)); QVector2D rightVisualTangent(m_d->itemDelegate->rightHandle(index, false)); if (leftVisualTangent.lengthSquared() > 0 && rightVisualTangent.lengthSquared() > 0) { float leftAngle = qAtan2(-leftVisualTangent.y(), -leftVisualTangent.x()); float rightAngle = qAtan2(rightVisualTangent.y(), rightVisualTangent.x()); float angle = (leftAngle + rightAngle) / 2; QVector2D unit(qCos(angle), qSin(angle)); leftVisualTangent = -unit * QVector2D(leftVisualTangent).length(); rightVisualTangent = unit * QVector2D(rightVisualTangent).length(); QPointF leftTangent = m_d->itemDelegate->unscaledTangent(leftVisualTangent.toPointF()); QPointF rightTangent = m_d->itemDelegate->unscaledTangent(rightVisualTangent.toPointF()); model()->setData(index, leftTangent, KisAnimationCurvesModel::LeftTangentRole); model()->setData(index, rightTangent, KisAnimationCurvesModel::RightTangentRole); } model()->setData(index, KisKeyframe::Smooth, KisAnimationCurvesModel::TangentsModeRole); } m_d->model->endCommand(); } void KisAnimationCurvesView::applySharpMode() { m_d->model->beginCommand(kundo2_i18n("Set interpolation mode")); Q_FOREACH(QModelIndex index, selectedIndexes()) { model()->setData(index, KisKeyframe::Sharp, KisAnimationCurvesModel::TangentsModeRole); } m_d->model->endCommand(); } void KisAnimationCurvesView::createKeyframe() { QModelIndex active = currentIndex(); int channel = active.isValid() ? active.row() : 0; int time = m_d->model->currentTime(); QModelIndex index = m_d->model->index(channel, time); qreal value = index.data(KisAnimationCurvesModel::ScalarValueRole).toReal(); m_d->model->setData(index, value, KisAnimationCurvesModel::ScalarValueRole); } void KisAnimationCurvesView::removeKeyframes() { m_d->model->removeFrames(selectedIndexes()); } void KisAnimationCurvesView::zoomToFit() { if (!model()) return; qreal minimum, maximum; findExtremes(&minimum, &maximum); if (minimum == maximum) return; qreal zoomLevel = (viewport()->height() - 2 * VERTICAL_PADDING) / (maximum - minimum); qreal offset = -VERTICAL_PADDING - zoomLevel * maximum; m_d->verticalHeader->setScale(zoomLevel); m_d->verticalHeader->setOffset(offset); verticalScrollBar()->setValue(offset); viewport()->update(); } diff --git a/plugins/dockers/animation/kis_zoom_button.cpp b/plugins/dockers/animation/kis_zoom_button.cpp index 2ead7d47da..c0eb407e57 100644 --- a/plugins/dockers/animation/kis_zoom_button.cpp +++ b/plugins/dockers/animation/kis_zoom_button.cpp @@ -1,70 +1,69 @@ /* * Copyright (c) 2016 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * 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_zoom_button.h" #include #include KisZoomButton::KisZoomButton(QWidget *parent) : KisDraggableToolButton(parent) - , m_zoomLevel(1.0) { connect(this, &KisZoomButton::valueChanged, this, &KisZoomButton::slotValueChanged); } KisZoomButton::~KisZoomButton() {} qreal KisZoomButton::zoomLevel() const { return m_zoomLevel; } void KisZoomButton::setZoomLevel(qreal level) { m_zoomLevel = level; } void KisZoomButton::beginZoom(QPoint mousePos, qreal staticPoint) { m_initialDragZoomLevel = m_zoomLevel; beginDrag(mousePos); emit zoomStarted(staticPoint); } void KisZoomButton::continueZoom(QPoint mousePos) { int delta = continueDrag(mousePos); slotValueChanged(delta); } void KisZoomButton::mousePressEvent(QMouseEvent *e) { beginZoom(e->pos(), qQNaN()); } void KisZoomButton::slotValueChanged(int value) { qreal zoomCoeff = std::pow(2.0, qreal(value) / unitRadius()); m_zoomLevel = m_initialDragZoomLevel * zoomCoeff; emit zoomLevelChanged(m_zoomLevel); } diff --git a/plugins/dockers/animation/kis_zoom_button.h b/plugins/dockers/animation/kis_zoom_button.h index 34aab3339c..2fd64f6e68 100644 --- a/plugins/dockers/animation/kis_zoom_button.h +++ b/plugins/dockers/animation/kis_zoom_button.h @@ -1,51 +1,51 @@ /* * Copyright (c) 2016 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public 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_ZOOM_BUTTON_H #define _KIS_ZOOM_BUTTON_H #include "kis_draggable_tool_button.h" class KisZoomButton : public KisDraggableToolButton { Q_OBJECT public: KisZoomButton(QWidget *parent); ~KisZoomButton() override; qreal zoomLevel() const; void setZoomLevel(qreal level); void beginZoom(QPoint mousePos, qreal staticPoint); void continueZoom(QPoint mousePos); void mousePressEvent(QMouseEvent *e) override; Q_SIGNALS: void zoomStarted(qreal staticPoint); void zoomLevelChanged(qreal level); private Q_SLOTS: void slotValueChanged(int value); private: - qreal m_zoomLevel; - qreal m_initialDragZoomLevel; + qreal m_zoomLevel {1.0}; + qreal m_initialDragZoomLevel {1.0}; }; #endif diff --git a/plugins/dockers/animation/tests/CMakeLists.txt b/plugins/dockers/animation/tests/CMakeLists.txt index a9683dc01e..cc9d500c27 100644 --- a/plugins/dockers/animation/tests/CMakeLists.txt +++ b/plugins/dockers/animation/tests/CMakeLists.txt @@ -1,12 +1,18 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_SOURCE_DIR}/sdk/tests ${CMAKE_CURRENT_BINARY_DIR}/..) macro_add_unittest_definitions() -########### next target ############### +include(KritaAddBrokenUnitTest) + +krita_add_broken_unit_test(timeline_model_test.cpp + TEST_NAME timeline_model_test.cpp + NAME_PREFIX "plugins-dockers-animation-" + LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaanimationdocker kritaui kritaimage Qt5::Test) ecm_add_tests( - timeline_model_test.cpp kis_animation_utils_test.cpp NAME_PREFIX "plugins-dockers-animation-" LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaanimationdocker kritaui kritaimage Qt5::Test) + + diff --git a/plugins/dockers/layerdocker/NodeView.h b/plugins/dockers/layerdocker/NodeView.h index abcd06d9af..489c2c1c85 100644 --- a/plugins/dockers/layerdocker/NodeView.h +++ b/plugins/dockers/layerdocker/NodeView.h @@ -1,187 +1,187 @@ /* Copyright (c) 2006 Gábor Lehel 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_DOCUMENT_SECTION_VIEW_H #define KIS_DOCUMENT_SECTION_VIEW_H #include #include class QStyleOptionViewItem; class KisNodeModel; /** * A widget displaying the Krita nodes (layers, masks, local selections, etc.) * * The widget can show the document sections as big thumbnails, * in a listview with two rows of informative text and icons, * or as single rows of text and property icons. * * This class is designed as a Qt model-view widget. * * The Qt documentation explains the design and terminology for these classes: - * http://doc.qt.io/qt-5/model-view-programming.html + * https://doc.qt.io/qt-5/model-view-programming.html * * This widget should work correctly in your Qt designer .ui file. */ class NodeView: public QTreeView { Q_OBJECT Q_SIGNALS: /** * Emitted whenever the user clicks with the secondary mouse * button on an item. It is up to the application to design the * contents of the context menu and show it. */ void contextMenuRequested(const QPoint &globalPos, const QModelIndex &index); void selectionChanged(const QModelIndexList &); public: /** * Create a new NodeView. */ explicit NodeView(QWidget *parent = 0); ~NodeView() override; /// how items should be displayed enum DisplayMode { /// large fit-to-width thumbnails, with only titles or page numbers ThumbnailMode, /// smaller thumbnails, with titles and property icons in two rows DetailedMode, /// no thumbnails, with titles and property icons in a single row MinimalMode }; void resizeEvent(QResizeEvent * event) override; void paintEvent (QPaintEvent *event) override; void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const override; void dropEvent(QDropEvent *ev) override; void dragEnterEvent(QDragEnterEvent *e) override; void dragMoveEvent(QDragMoveEvent *ev) override; void dragLeaveEvent(QDragLeaveEvent *e) override; /** * Set the display mode of the view to one of the options. * * @param mode The NodeView::DisplayMode mode */ void setDisplayMode(DisplayMode mode); /** * @return the currently active display mode */ DisplayMode displayMode() const; /** * Add toggle actions for all the properties associated with the * current document section associated with the model index to the * specified menu. * * For instance, if a document section can be locked and visible, * the menu will be expanded with locked and visible toggle * actions. * * For instance @code NodeView * nodeView; QModelIndex index = getCurrentNode(); QMenu menu; if (index.isValid()) { sectionView->addPropertyActions(&menu, index); } else { menu.addAction(...); // Something to create a new document section, for example. } @endcode * * @param menu A pointer to the menu that will be expanded with * the toglge actions * @param index The model index associated with the document * section that may or may not provide a number of toggle actions. */ void addPropertyActions(QMenu *menu, const QModelIndex &index); void updateNode(const QModelIndex &index); QRect originalVisualRect(const QModelIndex &index) const; protected: QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex &index, const QEvent *event) const override; QRect visualRect(const QModelIndex &index) const override; QModelIndex indexAt(const QPoint &point) const override; bool viewportEvent(QEvent *event) override; void contextMenuEvent(QContextMenuEvent *event) override; virtual void showContextMenu(const QPoint &globalPos, const QModelIndex &index); void startDrag (Qt::DropActions supportedActions) override; QPixmap createDragPixmap() const; /** * Calculates the index of the nearest item to the cursor position */ int cursorPageIndex() const; public Q_SLOTS: /// called with a theme change to refresh icon colors void slotUpdateIcons(); void slotScrollerStateChanged(QScroller::State state); protected Q_SLOTS: void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) override; void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()) override; void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) override; private Q_SLOTS: void slotActionToggled(bool on, const QPersistentModelIndex &index, int property); private: /** * Permit to know if a slide is dragging * * @return boolean */ bool isDragging() const; /** * Setter for the dragging flag * * @param flag boolean */ void setDraggingFlag(bool flag = true); bool m_draggingFlag; QStyleOptionViewItem optionForIndex(const QModelIndex &index) const; typedef KisNodeModel Model; class PropertyAction; class Private; Private* const d; }; #endif diff --git a/plugins/dockers/lut/lutdocker_dock.cpp b/plugins/dockers/lut/lutdocker_dock.cpp index ccd665216d..c7e68dce06 100644 --- a/plugins/dockers/lut/lutdocker_dock.cpp +++ b/plugins/dockers/lut/lutdocker_dock.cpp @@ -1,685 +1,685 @@ /* * 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 "lutdocker_dock.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_icon_utils.h" #include #include #include #include #include #include #include #include #include #include "kis_signals_blocker.h" #include "krita_utils.h" #include "ocio_display_filter.h" #include "black_white_point_chooser.h" #include "KisOcioConfiguration.h" #include OCIO::ConstConfigRcPtr defaultRawProfile() { /** * Copied from OCIO, just a noop profile */ const char * INTERNAL_RAW_PROFILE = "ocio_profile_version: 1\n" "strictparsing: false\n" "roles:\n" " default: raw\n" "displays:\n" " sRGB:\n" " - ! {name: Raw, colorspace: raw}\n" "colorspaces:\n" " - !\n" " name: raw\n" " family: raw\n" " equalitygroup:\n" " bitdepth: 32f\n" " isdata: true\n" " allocation: uniform\n" " description: 'A raw color space. Conversions to and from this space are no-ops.'\n"; std::istringstream istream; istream.str(INTERNAL_RAW_PROFILE); return OCIO::Config::CreateFromStream(istream); } LutDockerDock::LutDockerDock() : QDockWidget(i18n("LUT Management")) , m_canvas(0) , m_draggingSlider(false) { using namespace std::placeholders; // For _1 m_exposureCompressor.reset( new KisSignalCompressorWithParam(40, std::bind(&LutDockerDock::setCurrentExposureImpl, this, _1))); m_gammaCompressor.reset( new KisSignalCompressorWithParam(40, std::bind(&LutDockerDock::setCurrentGammaImpl, this, _1))); m_page = new QWidget(this); setupUi(m_page); setWidget(m_page); KisConfig cfg(true); m_chkUseOcio->setChecked(cfg.useOcio()); connect(m_chkUseOcio, SIGNAL(toggled(bool)), SLOT(updateDisplaySettings())); connect(m_colorManagement, SIGNAL(currentIndexChanged(int)), SLOT(slotColorManagementModeChanged())); m_bnSelectConfigurationFile->setToolTip(i18n("Select custom configuration file.")); connect(m_bnSelectConfigurationFile,SIGNAL(clicked()), SLOT(selectOcioConfiguration())); KisOcioConfiguration ocioOptions = cfg.ocioConfiguration(); m_txtConfigurationPath->setText(ocioOptions.configurationPath); m_txtLut->setText(ocioOptions.lutPath); m_bnSelectLut->setToolTip(i18n("Select LUT file")); connect(m_bnSelectLut, SIGNAL(clicked()), SLOT(selectLut())); connect(m_bnClearLut, SIGNAL(clicked()), SLOT(clearLut())); - // See http://groups.google.com/group/ocio-dev/browse_thread/thread/ec95c5f54a74af65 -- maybe need to be reinstated + // See https://groups.google.com/group/ocio-dev/browse_thread/thread/ec95c5f54a74af65 -- maybe need to be reinstated // when people ask for it. m_lblLut->hide(); m_txtLut->hide(); m_bnSelectLut->hide(); m_bnClearLut->hide(); connect(m_cmbDisplayDevice, SIGNAL(currentIndexChanged(int)), SLOT(refillViewCombobox())); m_exposureDoubleWidget->setToolTip(i18n("Select the exposure (stops) for HDR images.")); m_exposureDoubleWidget->setRange(-10, 10); m_exposureDoubleWidget->setPrecision(1); m_exposureDoubleWidget->setValue(0.0); m_exposureDoubleWidget->setSingleStep(0.25); m_exposureDoubleWidget->setPageStep(1); connect(m_exposureDoubleWidget, SIGNAL(valueChanged(double)), SLOT(exposureValueChanged(double))); connect(m_exposureDoubleWidget, SIGNAL(sliderPressed()), SLOT(exposureSliderPressed())); connect(m_exposureDoubleWidget, SIGNAL(sliderReleased()), SLOT(exposureSliderReleased())); // Gamma needs to be exponential (gamma *= 1.1f, gamma /= 1.1f as steps) m_gammaDoubleWidget->setToolTip(i18n("Select the amount of gamma modification for display. This does not affect the pixels of your image.")); m_gammaDoubleWidget->setRange(0.1, 5); m_gammaDoubleWidget->setPrecision(2); m_gammaDoubleWidget->setValue(1.0); m_gammaDoubleWidget->setSingleStep(0.1); m_gammaDoubleWidget->setPageStep(1); connect(m_gammaDoubleWidget, SIGNAL(valueChanged(double)), SLOT(gammaValueChanged(double))); connect(m_gammaDoubleWidget, SIGNAL(sliderPressed()), SLOT(gammaSliderPressed())); connect(m_gammaDoubleWidget, SIGNAL(sliderReleased()), SLOT(gammaSliderReleased())); m_bwPointChooser = new BlackWhitePointChooser(this); connect(m_bwPointChooser, SIGNAL(sigBlackPointChanged(qreal)), SLOT(updateDisplaySettings())); connect(m_bwPointChooser, SIGNAL(sigWhitePointChanged(qreal)), SLOT(updateDisplaySettings())); connect(m_btnConvertCurrentColor, SIGNAL(toggled(bool)), SLOT(updateDisplaySettings())); connect(m_btmShowBWConfiguration, SIGNAL(clicked()), SLOT(slotShowBWConfiguration())); slotUpdateIcons(); connect(m_cmbInputColorSpace, SIGNAL(currentIndexChanged(int)), SLOT(updateDisplaySettings())); connect(m_cmbDisplayDevice, SIGNAL(currentIndexChanged(int)), SLOT(updateDisplaySettings())); connect(m_cmbView, SIGNAL(currentIndexChanged(int)), SLOT(updateDisplaySettings())); connect(m_cmbLook, SIGNAL(currentIndexChanged(int)), SLOT(updateDisplaySettings())); connect(m_cmbComponents, SIGNAL(currentIndexChanged(int)), SLOT(updateDisplaySettings())); m_draggingSlider = false; connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(resetOcioConfiguration())); resetOcioConfiguration(); } LutDockerDock::~LutDockerDock() { } void LutDockerDock::setCanvas(KoCanvasBase* _canvas) { if (m_canvas) { m_canvas->disconnect(this); } setEnabled(_canvas != 0); if (KisCanvas2* canvas = dynamic_cast(_canvas)) { m_canvas = canvas; if (m_canvas) { if (!m_canvas->displayFilter()) { resetOcioConfiguration(); updateDisplaySettings(); } else { m_displayFilter = m_canvas->displayFilter(); OcioDisplayFilter *displayFilter = qobject_cast(m_displayFilter.data()); Q_ASSERT(displayFilter); m_ocioConfig = displayFilter->config; KisSignalsBlocker exposureBlocker(m_exposureDoubleWidget); m_exposureDoubleWidget->setValue(displayFilter->exposure); KisSignalsBlocker gammaBlocker(m_gammaDoubleWidget); m_gammaDoubleWidget->setValue(displayFilter->gamma); KisSignalsBlocker componentsBlocker(m_cmbComponents); m_cmbComponents->setCurrentIndex((int)displayFilter->swizzle); KisSignalsBlocker bwBlocker(m_bwPointChooser); m_bwPointChooser->setBlackPoint(displayFilter->blackPoint); m_bwPointChooser->setWhitePoint(displayFilter->whitePoint); } connect(m_canvas->image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), SLOT(slotImageColorSpaceChanged()), Qt::UniqueConnection); connect(m_canvas->viewManager()->mainWindow(), SIGNAL(themeChanged()), SLOT(slotUpdateIcons()), Qt::UniqueConnection); } } } void LutDockerDock::unsetCanvas() { m_canvas = 0; setEnabled(false); m_displayFilter = QSharedPointer(0); } void LutDockerDock::slotUpdateIcons() { m_btnConvertCurrentColor->setIcon(KisIconUtils::loadIcon("krita_tool_freehand")); m_btmShowBWConfiguration->setIcon(KisIconUtils::loadIcon("properties")); } void LutDockerDock::slotShowBWConfiguration() { m_bwPointChooser->showPopup(m_btmShowBWConfiguration->mapToGlobal(QPoint())); } bool LutDockerDock::canChangeExposureAndGamma() const { if (!m_chkUseOcio->isChecked() || !m_ocioConfig) return false; const bool externalColorManagementEnabled = m_colorManagement->currentIndex() != (int)KisOcioConfiguration::INTERNAL; #ifdef HAVE_HDR #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) KisSurfaceColorSpace currentColorSpace = KisOpenGLModeProber::instance()->surfaceformatInUse().colorSpace(); #else KisSurfaceColorSpace currentColorSpace = KisSurfaceColorSpace::DefaultColorSpace; #endif #endif const bool exposureManagementEnabled = externalColorManagementEnabled #ifdef HAVE_HDR || currentColorSpace == KisSurfaceColorSpace::scRGBColorSpace #endif ; return exposureManagementEnabled; } qreal LutDockerDock::currentExposure() const { if (!m_displayFilter) return 0.0; OcioDisplayFilter *displayFilter = qobject_cast(m_displayFilter.data()); return canChangeExposureAndGamma() ? displayFilter->exposure : 0.0; } void LutDockerDock::setCurrentExposure(qreal value) { if (!canChangeExposureAndGamma()) return; m_exposureCompressor->start(value); } qreal LutDockerDock::currentGamma() const { if (!m_displayFilter) return 1.0; OcioDisplayFilter *displayFilter = qobject_cast(m_displayFilter.data()); return canChangeExposureAndGamma() ? displayFilter->gamma : 1.0; } void LutDockerDock::setCurrentGamma(qreal value) { if (!canChangeExposureAndGamma()) return; m_gammaCompressor->start(value); } void LutDockerDock::setCurrentExposureImpl(qreal value) { m_exposureDoubleWidget->setValue(value); if (!m_canvas) return; m_canvas->viewManager()->showFloatingMessage( i18nc("floating message about exposure", "Exposure: %1", KritaUtils::prettyFormatReal(m_exposureDoubleWidget->value())), QIcon(), 500, KisFloatingMessage::Low); } void LutDockerDock::setCurrentGammaImpl(qreal value) { m_gammaDoubleWidget->setValue(value); if (!m_canvas) return; m_canvas->viewManager()->showFloatingMessage( i18nc("floating message about gamma", "Gamma: %1", KritaUtils::prettyFormatReal(m_gammaDoubleWidget->value())), QIcon(), 500, KisFloatingMessage::Low); } void LutDockerDock::slotImageColorSpaceChanged() { enableControls(); writeControls(); resetOcioConfiguration(); } void LutDockerDock::exposureValueChanged(double exposure) { if (m_canvas && !m_draggingSlider) { m_canvas->viewManager()->canvasResourceProvider()->setHDRExposure(exposure); updateDisplaySettings(); } } void LutDockerDock::exposureSliderPressed() { m_draggingSlider = true; } void LutDockerDock::exposureSliderReleased() { m_draggingSlider = false; exposureValueChanged(m_exposureDoubleWidget->value()); } void LutDockerDock::gammaValueChanged(double gamma) { if (m_canvas && !m_draggingSlider) { m_canvas->viewManager()->canvasResourceProvider()->setHDRGamma(gamma); updateDisplaySettings(); } } void LutDockerDock::gammaSliderPressed() { m_draggingSlider = true; } void LutDockerDock::gammaSliderReleased() { m_draggingSlider = false; gammaValueChanged(m_gammaDoubleWidget->value()); } void LutDockerDock::enableControls() { bool canDoExternalColorCorrection = false; if (m_canvas) { KisImageSP image = m_canvas->viewManager()->image(); canDoExternalColorCorrection = image->colorSpace()->colorModelId() == RGBAColorModelID; } if (!canDoExternalColorCorrection) { KisSignalsBlocker colorManagementBlocker(m_colorManagement); Q_UNUSED(colorManagementBlocker); m_colorManagement->setCurrentIndex((int) KisOcioConfiguration::INTERNAL); } const bool ocioEnabled = m_chkUseOcio->isChecked(); m_colorManagement->setEnabled(ocioEnabled && canDoExternalColorCorrection); const bool externalColorManagementEnabled = m_colorManagement->currentIndex() != (int)KisOcioConfiguration::INTERNAL; m_lblInputColorSpace->setEnabled(ocioEnabled && externalColorManagementEnabled); m_cmbInputColorSpace->setEnabled(ocioEnabled && externalColorManagementEnabled); m_lblDisplayDevice->setEnabled(ocioEnabled && externalColorManagementEnabled); m_cmbDisplayDevice->setEnabled(ocioEnabled && externalColorManagementEnabled); m_lblView->setEnabled(ocioEnabled && externalColorManagementEnabled); m_cmbView->setEnabled(ocioEnabled && externalColorManagementEnabled); m_lblLook->setEnabled(ocioEnabled && externalColorManagementEnabled); m_cmbLook->setEnabled(ocioEnabled && externalColorManagementEnabled); const bool exposureManagementEnabled = canChangeExposureAndGamma(); m_exposureDoubleWidget->setEnabled(exposureManagementEnabled); m_gammaDoubleWidget->setEnabled(exposureManagementEnabled); m_lblExposure->setEnabled(exposureManagementEnabled); m_lblGamma->setEnabled(exposureManagementEnabled); QString exposureToolTip; if (!exposureManagementEnabled) { exposureToolTip = i18nc("@info:tooltip", "Exposure and Gamma corrections are disabled in Internal mode. Switch to OCIO mode to use them"); } m_exposureDoubleWidget->setToolTip(exposureToolTip); m_gammaDoubleWidget->setToolTip(exposureToolTip); m_lblExposure->setToolTip(exposureToolTip); m_lblGamma->setToolTip(exposureToolTip); bool enableConfigPath = m_colorManagement->currentIndex() == (int) KisOcioConfiguration::OCIO_CONFIG; lblConfig->setEnabled(ocioEnabled && enableConfigPath); m_txtConfigurationPath->setEnabled(ocioEnabled && enableConfigPath); m_bnSelectConfigurationFile->setEnabled(ocioEnabled && enableConfigPath); } void LutDockerDock::updateDisplaySettings() { if (!m_canvas || !m_canvas->viewManager() || !m_canvas->viewManager()->image()) { return; } enableControls(); writeControls(); if (m_chkUseOcio->isChecked() && m_ocioConfig) { KIS_SAFE_ASSERT_RECOVER_NOOP(!m_canvas->displayFilter() || m_canvas->displayFilter() == m_displayFilter); if (!m_displayFilter) { m_displayFilter = m_canvas->displayFilter() ? m_canvas->displayFilter() : QSharedPointer(new OcioDisplayFilter(this)); } OcioDisplayFilter *displayFilter = qobject_cast(m_displayFilter.data()); displayFilter->config = m_ocioConfig; displayFilter->inputColorSpaceName = m_ocioConfig->getColorSpaceNameByIndex(m_cmbInputColorSpace->currentIndex()); displayFilter->displayDevice = m_ocioConfig->getDisplay(m_cmbDisplayDevice->currentIndex()); displayFilter->view = m_ocioConfig->getView(displayFilter->displayDevice, m_cmbView->currentIndex()); displayFilter->look = m_ocioConfig->getLookNameByIndex(m_cmbLook->currentIndex()); displayFilter->gamma = m_gammaDoubleWidget->isEnabled() ? m_gammaDoubleWidget->value() : 1.0; displayFilter->exposure = m_exposureDoubleWidget->isEnabled() ? m_exposureDoubleWidget->value() : 0.0; displayFilter->swizzle = (OCIO_CHANNEL_SWIZZLE)m_cmbComponents->currentIndex(); displayFilter->blackPoint = m_bwPointChooser->blackPoint(); displayFilter->whitePoint = m_bwPointChooser->whitePoint(); displayFilter->forceInternalColorManagement = m_colorManagement->currentIndex() == (int)KisOcioConfiguration::INTERNAL; displayFilter->setLockCurrentColorVisualRepresentation(m_btnConvertCurrentColor->isChecked()); displayFilter->updateProcessor(); m_canvas->setDisplayFilter(m_displayFilter); } else { m_canvas->setDisplayFilter(QSharedPointer(0)); } m_canvas->updateCanvas(); } void LutDockerDock::writeControls() { KisOcioConfiguration ocioOptions; ocioOptions.mode = (KisOcioConfiguration::Mode)m_colorManagement->currentIndex(); ocioOptions.configurationPath = m_txtConfigurationPath->text(); ocioOptions.lutPath = m_txtLut->text(); ocioOptions.inputColorSpace = m_cmbInputColorSpace->currentUnsqueezedText(); ocioOptions.displayDevice = m_cmbDisplayDevice->currentUnsqueezedText(); ocioOptions.displayView = m_cmbView->currentUnsqueezedText(); ocioOptions.look = m_cmbLook->currentUnsqueezedText(); KisConfig cfg(false); cfg.setUseOcio(m_chkUseOcio->isChecked()); cfg.setOcioConfiguration(ocioOptions); cfg.setOcioLockColorVisualRepresentation(m_btnConvertCurrentColor->isChecked()); } void LutDockerDock::slotColorManagementModeChanged() { enableControls(); writeControls(); resetOcioConfiguration(); } void LutDockerDock::selectOcioConfiguration() { QString filename = m_txtConfigurationPath->text(); KoFileDialog dialog(this, KoFileDialog::OpenFile, "lutdocker"); dialog.setCaption(i18n("Select OpenColorIO Configuration")); dialog.setDefaultDir(QDir::cleanPath(filename.isEmpty() ? QDir::homePath() : filename)); dialog.setMimeTypeFilters(QStringList() << "application/x-opencolorio-configuration"); filename = dialog.filename(); QFile f(filename); if (f.exists()) { m_txtConfigurationPath->setText(filename); writeControls(); resetOcioConfiguration(); } } void LutDockerDock::resetOcioConfiguration() { KisConfig cfg(true); KisOcioConfiguration ocioOptions = cfg.ocioConfiguration(); m_ocioConfig.reset(); try { if (ocioOptions.mode == KisOcioConfiguration::INTERNAL) { m_ocioConfig = defaultRawProfile(); } else if (ocioOptions.mode == KisOcioConfiguration::OCIO_ENVIRONMENT) { m_ocioConfig = OCIO::Config::CreateFromEnv(); } else if (ocioOptions.mode == KisOcioConfiguration::OCIO_CONFIG) { QString configFile = ocioOptions.configurationPath; if (QFile::exists(configFile)) { m_ocioConfig = OCIO::Config::CreateFromFile(configFile.toUtf8()); } else { m_ocioConfig = defaultRawProfile(); } } if (m_ocioConfig) { OCIO::SetCurrentConfig(m_ocioConfig); } } catch (OCIO::Exception &exception) { dbgKrita << "OpenColorIO Error:" << exception.what() << "Cannot create the LUT docker"; } if (m_ocioConfig) { refillControls(); } } void LutDockerDock::refillControls() { if (!m_canvas) return; if (!m_canvas->viewManager()) return; if (!m_canvas->viewManager()->canvasResourceProvider()) return; if (!m_canvas->viewManager()->image()) return; KIS_ASSERT_RECOVER_RETURN(m_ocioConfig); KisConfig cfg(true); KisOcioConfiguration ocioOptions = cfg.ocioConfiguration(); { // Color Management Mode KisSignalsBlocker modeBlocker(m_colorManagement); m_colorManagement->setCurrentIndex((int) ocioOptions.mode); } { // Exposure KisSignalsBlocker exposureBlocker(m_exposureDoubleWidget); m_exposureDoubleWidget->setValue(m_canvas->viewManager()->canvasResourceProvider()->HDRExposure()); } { // Gamma KisSignalsBlocker gammaBlocker(m_gammaDoubleWidget); m_gammaDoubleWidget->setValue(m_canvas->viewManager()->canvasResourceProvider()->HDRGamma()); } { // Components const KoColorSpace *cs = m_canvas->viewManager()->image()->colorSpace(); QStringList itemsList; itemsList << i18n("Luminance"); itemsList << i18n("All Channels"); Q_FOREACH (KoChannelInfo *channel, KoChannelInfo::displayOrderSorted(cs->channels())) { itemsList << channel->name(); } if (m_cmbComponents->originalTexts() != itemsList) { KisSignalsBlocker componentsBlocker(m_cmbComponents); m_cmbComponents->resetOriginalTexts(itemsList); m_cmbComponents->setCurrentIndex(1); // All Channels... } } { // Input Color Space QStringList itemsList; int numOcioColorSpaces = m_ocioConfig->getNumColorSpaces(); for(int i = 0; i < numOcioColorSpaces; ++i) { const char *cs = m_ocioConfig->getColorSpaceNameByIndex(i); OCIO::ConstColorSpaceRcPtr colorSpace = m_ocioConfig->getColorSpace(cs); itemsList << QString::fromUtf8(colorSpace->getName()); } KisSignalsBlocker inputCSBlocker(m_cmbInputColorSpace); if (itemsList != m_cmbInputColorSpace->originalTexts()) { m_cmbInputColorSpace->resetOriginalTexts(itemsList); } m_cmbInputColorSpace->setCurrent(ocioOptions.inputColorSpace); } { // Display Device QStringList itemsList; int numDisplays = m_ocioConfig->getNumDisplays(); for (int i = 0; i < numDisplays; ++i) { itemsList << QString::fromUtf8(m_ocioConfig->getDisplay(i)); } KisSignalsBlocker displayDeviceLocker(m_cmbDisplayDevice); if (itemsList != m_cmbDisplayDevice->originalTexts()) { m_cmbDisplayDevice->resetOriginalTexts(itemsList); } m_cmbDisplayDevice->setCurrent(ocioOptions.displayDevice); } { // Lock Current Color KisSignalsBlocker locker(m_btnConvertCurrentColor); m_btnConvertCurrentColor->setChecked(cfg.ocioLockColorVisualRepresentation()); } refillViewCombobox(); { QStringList itemsList; int numLooks = m_ocioConfig->getNumLooks(); for (int k = 0; k < numLooks; k++) { itemsList << QString::fromUtf8(m_ocioConfig->getLookNameByIndex(k)); } itemsList << i18nc("Item to indicate no look transform being selected","None"); KisSignalsBlocker LookComboLocker(m_cmbLook); if (itemsList != m_cmbLook->originalTexts()) { m_cmbLook->resetOriginalTexts(itemsList); } m_cmbLook->setCurrent(ocioOptions.look); } updateDisplaySettings(); } void LutDockerDock::refillViewCombobox() { KisSignalsBlocker viewComboLocker(m_cmbView); m_cmbView->clear(); if (!m_canvas || !m_ocioConfig) return; const char *display = m_ocioConfig->getDisplay(m_cmbDisplayDevice->currentIndex()); int numViews = m_ocioConfig->getNumViews(display); for (int j = 0; j < numViews; ++j) { m_cmbView->addSqueezedItem(QString::fromUtf8(m_ocioConfig->getView(display, j))); } KisConfig cfg(true); KisOcioConfiguration ocioOptions = cfg.ocioConfiguration(); m_cmbView->setCurrent(ocioOptions.displayView); } void LutDockerDock::selectLut() { QString filename = m_txtLut->text(); KoFileDialog dialog(this, KoFileDialog::OpenFile, "lutdocker"); dialog.setCaption(i18n("Select LUT file")); dialog.setDefaultDir(QDir::cleanPath(filename)); dialog.setMimeTypeFilters(QStringList() << "application/octet-stream", "application/octet-stream"); filename = dialog.filename(); QFile f(filename); if (f.exists() && filename != m_txtLut->text()) { m_txtLut->setText(filename); writeControls(); updateDisplaySettings(); } } void LutDockerDock::clearLut() { m_txtLut->clear(); updateDisplaySettings(); } diff --git a/plugins/dockers/presethistory/presethistory_dock.cpp b/plugins/dockers/presethistory/presethistory_dock.cpp index 5a80109735..d01e9335ff 100644 --- a/plugins/dockers/presethistory/presethistory_dock.cpp +++ b/plugins/dockers/presethistory/presethistory_dock.cpp @@ -1,151 +1,151 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; 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 "presethistory_dock.h" #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_canvas2.h" #include "KisViewManager.h" #include "kis_paintop_box.h" #include "kis_paintop_presets_chooser_popup.h" #include "kis_canvas_resource_provider.h" #include "KisResourceServerProvider.h" #include #include #include #define ICON_SIZE 48 PresetHistoryDock::PresetHistoryDock( ) : QDockWidget(i18n("Brush Preset History")) , m_canvas(0) , m_block(false) , m_initialized(false) { m_presetHistory = new QListWidget(this); m_presetHistory->setIconSize(QSize(ICON_SIZE, ICON_SIZE)); m_presetHistory->setDragEnabled(false); m_presetHistory->setSelectionBehavior(QAbstractItemView::SelectRows); m_presetHistory->setSelectionMode(QAbstractItemView::SingleSelection); m_presetHistory->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); setWidget(m_presetHistory); QScroller* scroller = KisKineticScroller::createPreconfiguredScroller(m_presetHistory); if( scroller ) { connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State))); } connect(m_presetHistory, SIGNAL(itemClicked(QListWidgetItem*)), SLOT(presetSelected(QListWidgetItem*))); } void PresetHistoryDock::setCanvas(KoCanvasBase * canvas) { setEnabled(canvas != 0); if (m_canvas) { m_canvas->disconnectCanvasObserver(this); disconnect(m_canvas->resourceManager()); } m_canvas = dynamic_cast(canvas); if (!m_canvas || !m_canvas->viewManager() || !m_canvas->resourceManager()) return; connect(m_canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), SLOT(canvasResourceChanged(int,QVariant))); if (!m_initialized) { KisConfig cfg(true); QStringList presetHistory = cfg.readEntry("presethistory", "").split(",", QString::SkipEmptyParts); KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); Q_FOREACH (const QString &p, presetHistory) { KisPaintOpPresetSP preset = rserver->resourceByName(p); addPreset(preset); } m_initialized = true; } } void PresetHistoryDock::unsetCanvas() { m_canvas = 0; setEnabled(false); QStringList presetHistory; for(int i = m_presetHistory->count() -1; i >=0; --i) { QListWidgetItem *item = m_presetHistory->item(i); QVariant v = item->data(Qt::UserRole); KisPaintOpPresetSP preset = v.value(); presetHistory << preset->name(); } KisConfig cfg(false); cfg.writeEntry("presethistory", presetHistory.join(",")); } void PresetHistoryDock::presetSelected(QListWidgetItem *item) { if (item) { QVariant v = item->data(Qt::UserRole); KisPaintOpPresetSP preset = v.value(); m_block = true; m_canvas->viewManager()->paintOpBox()->resourceSelected(preset.data()); m_block = false; } } void PresetHistoryDock::canvasResourceChanged(int key, const QVariant& /*v*/) { if (m_block) return; if (m_canvas && key == KisCanvasResourceProvider::CurrentPaintOpPreset) { KisPaintOpPresetSP preset = m_canvas->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (preset) { for (int i = 0; i < m_presetHistory->count(); ++i) { if (preset->name() == m_presetHistory->item(i)->text()) { m_presetHistory->setCurrentRow(i); return; } } addPreset(preset); } } } void PresetHistoryDock::addPreset(KisPaintOpPresetSP preset) { if (preset) { QListWidgetItem *item = new QListWidgetItem(QPixmap::fromImage(preset->image()), preset->name()); QVariant v = QVariant::fromValue(preset); item->setData(Qt::UserRole, v); m_presetHistory->insertItem(0, item); m_presetHistory->setCurrentRow(0); if (m_presetHistory->count() > 10) { - m_presetHistory->takeItem(10); + delete m_presetHistory->takeItem(10); } } } diff --git a/plugins/dockers/touchdocker/TouchDockerDock.cpp b/plugins/dockers/touchdocker/TouchDockerDock.cpp index c81463d8a5..f1b5830d10 100644 --- a/plugins/dockers/touchdocker/TouchDockerDock.cpp +++ b/plugins/dockers/touchdocker/TouchDockerDock.cpp @@ -1,429 +1,433 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; 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 "TouchDockerDock.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 namespace { bool shouldSetAcceptTouchEvents() { // See https://bugreports.qt.io/browse/QTBUG-66718 static QVersionNumber qtVersion = QVersionNumber::fromString(qVersion()); static bool retval = qtVersion > QVersionNumber(5, 9, 3) && qtVersion.normalized() != QVersionNumber(5, 10); return retval; } } // namespace class TouchDockerDock::Private { public: Private() { } TouchDockerDock *q; bool allowClose {true}; KisSketchView *sketchView {0}; QString currentSketchPage; KoDialog *openDialog {0}; KoDialog *saveAsDialog {0}; QMap buttonMapping; bool shiftOn {false}; bool ctrlOn {false}; bool altOn {false}; }; TouchDockerDock::TouchDockerDock() : QDockWidget(i18n("Touch Docker")) , d(new Private()) { QStringList defaultMapping = QStringList() << "decrease_opacity" << "increase_opacity" << "make_brush_color_lighter" << "make_brush_color_darker" << "decrease_brush_size" << "increase_brush_size" << "previous_preset" << "clear"; QStringList mapping = KisConfig(true).readEntry("touchdockermapping", defaultMapping.join(',')).split(','); for (int i = 0; i < 8; ++i) { if (i < mapping.size()) { d->buttonMapping[QString("button%1").arg(i + 1)] = mapping[i]; } else if (i < defaultMapping.size()) { d->buttonMapping[QString("button%1").arg(i + 1)] = defaultMapping[i]; } } m_quickWidget = new QQuickWidget(this); if (shouldSetAcceptTouchEvents()) { m_quickWidget->setAttribute(Qt::WA_AcceptTouchEvents); } setWidget(m_quickWidget); setEnabled(true); m_quickWidget->engine()->rootContext()->setContextProperty("mainWindow", this); m_quickWidget->engine()->addImportPath(KoResourcePaths::getApplicationRoot() + "/lib/qml/"); m_quickWidget->engine()->addImportPath(KoResourcePaths::getApplicationRoot() + "/lib64/qml/"); m_quickWidget->engine()->addPluginPath(KoResourcePaths::getApplicationRoot() + "/lib/qml/"); m_quickWidget->engine()->addPluginPath(KoResourcePaths::getApplicationRoot() + "/lib64/qml/"); Settings *settings = new Settings(this); DocumentManager::instance()->setSettingsManager(settings); m_quickWidget->engine()->rootContext()->setContextProperty("Settings", settings); Theme *theme = Theme::load(KSharedConfig::openConfig()->group("General").readEntry("theme", "default"), m_quickWidget->engine()); if (theme) { settings->setTheme(theme); } m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); m_quickWidget->setSource(QUrl("qrc:/touchstrip.qml")); } TouchDockerDock::~TouchDockerDock() { } bool TouchDockerDock::allowClose() const { return d->allowClose; } void TouchDockerDock::setAllowClose(bool allow) { d->allowClose = allow; } QString TouchDockerDock::currentSketchPage() const { return d->currentSketchPage; } void TouchDockerDock::setCurrentSketchPage(QString newPage) { d->currentSketchPage = newPage; emit currentSketchPageChanged(); } void TouchDockerDock::closeEvent(QCloseEvent* event) { if (!d->allowClose) { event->ignore(); emit closeRequested(); } else { event->accept(); } } void TouchDockerDock::slotButtonPressed(const QString &id) { if (id == "fileOpenButton") { showFileOpenDialog(); } else if (id == "fileSaveButton" && m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->document()) { - bool batchMode = m_canvas->viewManager()->document()->fileBatchMode(); - m_canvas->viewManager()->document()->setFileBatchMode(true); - m_canvas->viewManager()->document()->save(true, 0); - m_canvas->viewManager()->document()->setFileBatchMode(batchMode); + if(m_canvas->viewManager()->document()->url().isEmpty()) { + showFileSaveAsDialog(); + } else { + bool batchMode = m_canvas->viewManager()->document()->fileBatchMode(); + m_canvas->viewManager()->document()->setFileBatchMode(true); + m_canvas->viewManager()->document()->save(true, 0); + m_canvas->viewManager()->document()->setFileBatchMode(batchMode); + } } else if (id == "fileSaveAsButton" && m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->document()) { showFileSaveAsDialog(); } else { QAction *a = action(id); if (a) { if (a->isCheckable()) { a->toggle(); } else { a->trigger(); } } else if (id == "shift") { // set shift state for the next pointer event, somehow QKeyEvent event(d->shiftOn ? QEvent::KeyRelease : QEvent::KeyPress, 0, Qt::ShiftModifier); QApplication::sendEvent(KisPart::instance()->currentMainwindow(), &event); d->shiftOn = !d->shiftOn; } else if (id == "ctrl") { // set ctrl state for the next pointer event, somehow QKeyEvent event(d->ctrlOn ? QEvent::KeyRelease : QEvent::KeyPress, 0, Qt::ControlModifier); QApplication::sendEvent(KisPart::instance()->currentMainwindow(), &event); d->ctrlOn = !d->ctrlOn; } else if (id == "alt") { // set alt state for the next pointer event, somehow QKeyEvent event(d->altOn ? QEvent::KeyRelease : QEvent::KeyPress, 0, Qt::AltModifier); QApplication::sendEvent(KisPart::instance()->currentMainwindow(), &event); d->altOn = !d->altOn; } } } void TouchDockerDock::slotOpenImage(QString path) { if (d->openDialog) { d->openDialog->accept(); } KisPart::instance()->currentMainwindow()->openDocument(QUrl::fromLocalFile(path), KisMainWindow::None); } void TouchDockerDock::slotSaveAs(QString path, QString mime) { if (d->saveAsDialog) { d->saveAsDialog->accept(); } m_canvas->viewManager()->document()->saveAs(QUrl::fromLocalFile(path), mime.toLatin1(), true); m_canvas->viewManager()->document()->waitForSavingToComplete(); } void TouchDockerDock::hideFileOpenDialog() { if (d->openDialog) { d->openDialog->accept(); } } void TouchDockerDock::hideFileSaveAsDialog() { if (d->saveAsDialog) { d->saveAsDialog->accept(); } } QString TouchDockerDock::imageForButton(QString id) { if (d->buttonMapping.contains(id)) { id = d->buttonMapping[id]; } if (KisActionRegistry::instance()->hasAction(id)) { QString a = KisActionRegistry::instance()->getActionProperty(id, "icon"); if (!a.isEmpty()) { return "image://icon/" + a; } } return QString(); } QString TouchDockerDock::textForButton(QString id) { if (d->buttonMapping.contains(id)) { id = d->buttonMapping[id]; } if (KisActionRegistry::instance()->hasAction(id)) { QString a = KisActionRegistry::instance()->getActionProperty(id, "iconText"); if (a.isEmpty()) { a = KisActionRegistry::instance()->getActionProperty(id, "text"); } return a; } return id; } QAction *TouchDockerDock::action(QString id) const { if (m_canvas && m_canvas->viewManager()) { if (d->buttonMapping.contains(id)) { id = d->buttonMapping[id]; } return m_canvas->viewManager()->actionManager()->actionByName(id); } return 0; } void TouchDockerDock::showFileOpenDialog() { if (!d->openDialog) { d->openDialog = createDialog("qrc:/opendialog.qml"); } d->openDialog->exec(); } void TouchDockerDock::showFileSaveAsDialog() { if (!d->saveAsDialog) { d->saveAsDialog = createDialog("qrc:/saveasdialog.qml"); } d->saveAsDialog->exec(); } void TouchDockerDock::changeEvent(QEvent *event) { if (event->type() == QEvent::PaletteChange) { m_quickWidget->setSource(QUrl("qrc:/touchstrip.qml")); event->accept(); } else { event->ignore(); } } void TouchDockerDock::tabletEvent(QTabletEvent *event) { #ifdef Q_OS_WIN /** * On Windows (only in WinInk mode), unless we accept the tablet event, * OS will start windows gestures, like click+hold for right click. * It will block any mouse events generation. * * In our own (hacky) implementation, if we accept the event, we block * the gesture, but still generate a fake mouse event. */ event->accept(); #else QDockWidget::tabletEvent(event); #endif } KoDialog *TouchDockerDock::createDialog(const QString qml) { KoDialog *dlg = new KoDialog(this); dlg->setButtons(KoDialog::None); QQuickWidget *quickWidget = new QQuickWidget(this); if (shouldSetAcceptTouchEvents()) { quickWidget->setAttribute(Qt::WA_AcceptTouchEvents); } dlg->setMainWidget(quickWidget); setEnabled(true); quickWidget->engine()->rootContext()->setContextProperty("mainWindow", this); quickWidget->engine()->addImportPath(KoResourcePaths::getApplicationRoot() + "/lib/qml/"); quickWidget->engine()->addImportPath(KoResourcePaths::getApplicationRoot() + "/lib64/qml/"); quickWidget->engine()->addPluginPath(KoResourcePaths::getApplicationRoot() + "/lib/qml/"); quickWidget->engine()->addPluginPath(KoResourcePaths::getApplicationRoot() + "/lib64/qml/"); Settings *settings = new Settings(this); DocumentManager::instance()->setSettingsManager(settings); quickWidget->engine()->rootContext()->setContextProperty("Settings", settings); Theme *theme = Theme::load(KSharedConfig::openConfig()->group("General").readEntry("theme", "default"), quickWidget->engine()); settings->setTheme(theme); quickWidget->setSource(QUrl(qml)); quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); dlg->setMinimumSize(1280, 768); return dlg; } QObject *TouchDockerDock::sketchKisView() const { return d->sketchView; } void TouchDockerDock::setSketchKisView(QObject* newView) { if (d->sketchView) { d->sketchView->disconnect(this); } if (d->sketchView != newView) { d->sketchView = qobject_cast(newView); emit sketchKisViewChanged(); } } void TouchDockerDock::setCanvas(KoCanvasBase *canvas) { setEnabled(true); if (m_canvas == canvas) { return; } if (m_canvas) { m_canvas->disconnectCanvasObserver(this); } if (!canvas) { m_canvas = 0; return; } m_canvas = dynamic_cast(canvas); } void TouchDockerDock::unsetCanvas() { setEnabled(true); m_canvas = 0; } diff --git a/plugins/extensions/animationrenderer/AnimationRenderer.cpp b/plugins/extensions/animationrenderer/AnimationRenderer.cpp index c053c3c537..b8229a2ce4 100644 --- a/plugins/extensions/animationrenderer/AnimationRenderer.cpp +++ b/plugins/extensions/animationrenderer/AnimationRenderer.cpp @@ -1,206 +1,208 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "AnimationRenderer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "DlgAnimationRenderer.h" #include #include "video_saver.h" #include "KisAnimationRenderingOptions.h" K_PLUGIN_FACTORY_WITH_JSON(AnimaterionRendererFactory, "kritaanimationrenderer.json", registerPlugin();) AnimaterionRenderer::AnimaterionRenderer(QObject *parent, const QVariantList &) : KisActionPlugin(parent) { // Shows the big dialog KisAction *action = createAction("render_animation"); action->setActivationFlags(KisAction::IMAGE_HAS_ANIMATION); connect(action, SIGNAL(triggered()), this, SLOT(slotRenderAnimation())); // Re-renders the image sequence as defined in the last render action = createAction("render_animation_again"); action->setActivationFlags(KisAction::IMAGE_HAS_ANIMATION); connect(action, SIGNAL(triggered()), this, SLOT(slotRenderSequenceAgain())); } AnimaterionRenderer::~AnimaterionRenderer() { } void AnimaterionRenderer::slotRenderAnimation() { KisImageWSP image = viewManager()->image(); if (!image) return; if (!image->animationInterface()->hasAnimation()) return; KisDocument *doc = viewManager()->document(); DlgAnimationRenderer dlgAnimationRenderer(doc, viewManager()->mainWindow()); dlgAnimationRenderer.setCaption(i18n("Render Animation")); if (dlgAnimationRenderer.exec() == QDialog::Accepted) { KisAnimationRenderingOptions encoderOptions = dlgAnimationRenderer.getEncoderOptions(); renderAnimationImpl(doc, encoderOptions); } } void AnimaterionRenderer::slotRenderSequenceAgain() { KisImageWSP image = viewManager()->image(); if (!image) return; if (!image->animationInterface()->hasAnimation()) return; KisDocument *doc = viewManager()->document(); KisConfig cfg(true); KisPropertiesConfigurationSP settings = cfg.exportConfiguration("ANIMATION_EXPORT"); KisAnimationRenderingOptions encoderOptions; encoderOptions.fromProperties(settings); renderAnimationImpl(doc, encoderOptions); } void AnimaterionRenderer::renderAnimationImpl(KisDocument *doc, KisAnimationRenderingOptions encoderOptions) { const QString frameMimeType = encoderOptions.frameMimeType; const QString framesDirectory = encoderOptions.resolveAbsoluteFramesDirectory(); const QString extension = KisMimeDatabase::suffixesForMimeType(frameMimeType).first(); const QString baseFileName = QString("%1/%2.%3").arg(framesDirectory) .arg(encoderOptions.basename) .arg(extension); /** * The dialog should ensure that the size of the video is even */ KIS_SAFE_ASSERT_RECOVER( !((encoderOptions.width & 0x1 || encoderOptions.height & 0x1) && (encoderOptions.videoMimeType == "video/mp4" || - encoderOptions.videoMimeType == "video/x-matroska"))) { + encoderOptions.videoMimeType == "video/x-matroska") && !(encoderOptions.renderMode() == encoderOptions.RENDER_FRAMES_ONLY))) { encoderOptions.width = encoderOptions.width + (encoderOptions.width & 0x1); encoderOptions.height = encoderOptions.height + (encoderOptions.height & 0x1); } const QSize scaledSize = doc->image()->bounds().size().scaled( encoderOptions.width, encoderOptions.height, Qt::KeepAspectRatio); + if ((scaledSize.width() & 0x1 || scaledSize.height() & 0x1) && (encoderOptions.videoMimeType == "video/mp4" || - encoderOptions.videoMimeType == "video/x-matroska")) { + encoderOptions.videoMimeType == "video/x-matroska") && !(encoderOptions.renderMode() == encoderOptions.RENDER_FRAMES_ONLY)) { QString m = "Mastroska (.mkv)"; if (encoderOptions.videoMimeType == "video/mp4") { m = "Mpeg4 (.mp4)"; } qWarning() << m <<"requires width and height to be even, resize and try again!"; doc->setErrorMessage(i18n("%1 requires width and height to be even numbers. Please resize or crop the image before exporting.", m)); QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", doc->errorMessage())); return; } + const bool batchMode = false; // TODO: fetch correctly! KisAsyncAnimationFramesSaveDialog exporter(doc->image(), KisTimeRange::fromTime(encoderOptions.firstFrame, encoderOptions.lastFrame), baseFileName, encoderOptions.sequenceStart, encoderOptions.frameExportConfig); exporter.setBatchMode(batchMode); KisAsyncAnimationFramesSaveDialog::Result result = exporter.regenerateRange(viewManager()->mainWindow()->viewManager()); // the folder could have been read-only or something else could happen if (encoderOptions.shouldEncodeVideo && result == KisAsyncAnimationFramesSaveDialog::RenderComplete) { const QString savedFilesMask = exporter.savedFilesMask(); const QString resultFile = encoderOptions.resolveAbsoluteVideoFilePath(); KIS_SAFE_ASSERT_RECOVER_NOOP(QFileInfo(resultFile).isAbsolute()); { const QFileInfo info(resultFile); QDir dir(info.absolutePath()); if (!dir.exists()) { dir.mkpath(info.absolutePath()); } KIS_SAFE_ASSERT_RECOVER_NOOP(dir.exists()); } KisImportExportErrorCode res; QFile fi(resultFile); if (!fi.open(QIODevice::WriteOnly)) { qWarning() << "Could not open" << fi.fileName() << "for writing!"; res = KisImportExportErrorCannotWrite(fi.error()); } else { fi.close(); } QScopedPointer encoder(new VideoSaver(doc, batchMode)); res = encoder->convert(doc, savedFilesMask, encoderOptions, batchMode); if (!res.isOk()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", res.errorMessage())); } if (encoderOptions.shouldDeleteSequence) { QDir d(framesDirectory); QStringList sequenceFiles = d.entryList(QStringList() << encoderOptions.basename + "*." + extension, QDir::Files); Q_FOREACH(const QString &f, sequenceFiles) { d.remove(f); } } } else if (result == KisAsyncAnimationFramesSaveDialog::RenderFailed) { viewManager()->mainWindow()->viewManager()->showFloatingMessage(i18n("Failed to render animation frames!"), QIcon()); } } #include "AnimationRenderer.moc" diff --git a/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp b/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp index a48d47335e..b76b16303b 100644 --- a/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp +++ b/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp @@ -1,594 +1,594 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "DlgAnimationRenderer.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_slider_spin_box.h" #include "kis_acyclic_signal_connector.h" #include "video_saver.h" #include "KisAnimationRenderingOptions.h" #include "video_export_options_dialog.h" DlgAnimationRenderer::DlgAnimationRenderer(KisDocument *doc, QWidget *parent) : KoDialog(parent) , m_image(doc->image()) , m_doc(doc) { KisConfig cfg(true); setCaption(i18n("Render Animation")); setButtons(Ok | Cancel); setDefaultButton(Ok); m_page = new WdgAnimationRenderer(this); m_page->layout()->setMargin(0); m_page->dirRequester->setMode(KoFileDialog::OpenDirectory); m_page->intStart->setMinimum(0); m_page->intStart->setMaximum(doc->image()->animationInterface()->fullClipRange().end()); m_page->intStart->setValue(doc->image()->animationInterface()->playbackRange().start()); m_page->intEnd->setMinimum(doc->image()->animationInterface()->fullClipRange().start()); // animators sometimes want to export after end frame //m_page->intEnd->setMaximum(doc->image()->animationInterface()->fullClipRange().end()); m_page->intEnd->setValue(doc->image()->animationInterface()->playbackRange().end()); m_page->intHeight->setMinimum(1); - m_page->intHeight->setMaximum(10000); + m_page->intHeight->setMaximum(100000); m_page->intHeight->setValue(doc->image()->height()); m_page->intWidth->setMinimum(1); - m_page->intWidth->setMaximum(10000); + m_page->intWidth->setMaximum(100000); m_page->intWidth->setValue(doc->image()->width()); // try to lock the width and height being updated KisAcyclicSignalConnector *constrainsConnector = new KisAcyclicSignalConnector(this); constrainsConnector->createCoordinatedConnector()->connectBackwardInt(m_page->intWidth, SIGNAL(valueChanged(int)), this, SLOT(slotLockAspectRatioDimensionsWidth(int))); constrainsConnector->createCoordinatedConnector()->connectForwardInt(m_page->intHeight, SIGNAL(valueChanged(int)), this, SLOT(slotLockAspectRatioDimensionsHeight(int))); m_page->intFramesPerSecond->setValue(doc->image()->animationInterface()->framerate()); QFileInfo audioFileInfo(doc->image()->animationInterface()->audioChannelFileName()); const bool hasAudio = audioFileInfo.exists(); m_page->chkIncludeAudio->setEnabled(hasAudio); m_page->chkIncludeAudio->setChecked(hasAudio && !doc->image()->animationInterface()->isAudioMuted()); QStringList mimes = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); mimes.sort(); Q_FOREACH(const QString &mime, mimes) { QString description = KisMimeDatabase::descriptionForMimeType(mime); if (description.isEmpty()) { description = mime; } m_page->cmbMimetype->addItem(description, mime); if (mime == "image/png") { m_page->cmbMimetype->setCurrentIndex(m_page->cmbMimetype->count() - 1); } } setMainWidget(m_page); QVector supportedMimeType; supportedMimeType << "video/x-matroska"; supportedMimeType << "image/gif"; supportedMimeType << "video/ogg"; supportedMimeType << "video/mp4"; Q_FOREACH (const QString &mime, supportedMimeType) { QString description = KisMimeDatabase::descriptionForMimeType(mime); if (description.isEmpty()) { description = mime; } m_page->cmbRenderType->addItem(description, mime); } m_page->videoFilename->setMode(KoFileDialog::SaveFile); connect(m_page->bnExportOptions, SIGNAL(clicked()), this, SLOT(sequenceMimeTypeOptionsClicked())); connect(m_page->bnRenderOptions, SIGNAL(clicked()), this, SLOT(selectRenderOptions())); m_page->ffmpegLocation->setMode(KoFileDialog::OpenFile); m_page->cmbRenderType->setCurrentIndex(cfg.readEntry("AnimationRenderer/render_type", 0)); connect(m_page->shouldExportOnlyImageSequence, SIGNAL(toggled(bool)), this, SLOT(slotExportTypeChanged())); connect(m_page->shouldExportOnlyVideo, SIGNAL(toggled(bool)), this, SLOT(slotExportTypeChanged())); connect(m_page->shouldExportAll, SIGNAL(toggled(bool)), this, SLOT(slotExportTypeChanged())); connect(m_page->intFramesPerSecond, SIGNAL(valueChanged(int)), SLOT(frameRateChanged(int))); // connect and cold init connect(m_page->cmbRenderType, SIGNAL(currentIndexChanged(int)), this, SLOT(selectRenderType(int))); selectRenderType(m_page->cmbRenderType->currentIndex()); resize(m_page->sizeHint()); connect(this, SIGNAL(accepted()), SLOT(slotDialogAccepted())); { KisPropertiesConfigurationSP settings = cfg.exportConfiguration("ANIMATION_EXPORT"); KisAnimationRenderingOptions options; options.fromProperties(settings); loadAnimationOptions(options); } } DlgAnimationRenderer::~DlgAnimationRenderer() { delete m_page; } void DlgAnimationRenderer::getDefaultVideoEncoderOptions(const QString &mimeType, KisPropertiesConfigurationSP cfg, QString *customFFMpegOptionsString, bool *forceHDRVideo) { const VideoExportOptionsDialog::ContainerType containerType = mimeType == "video/ogg" ? VideoExportOptionsDialog::OGV : VideoExportOptionsDialog::DEFAULT; QScopedPointer encoderConfigWidget( new VideoExportOptionsDialog(containerType, 0)); // we always enable HDR, letting the user to force it encoderConfigWidget->setSupportsHDR(true); encoderConfigWidget->setConfiguration(cfg); *customFFMpegOptionsString = encoderConfigWidget->customUserOptionsString(); *forceHDRVideo = encoderConfigWidget->forceHDRModeForFrames(); } void DlgAnimationRenderer::loadAnimationOptions(const KisAnimationRenderingOptions &options) { const QString documentPath = m_doc->localFilePath(); m_page->txtBasename->setText(options.basename); if (!options.lastDocuemntPath.isEmpty() && options.lastDocuemntPath == documentPath) { m_page->intStart->setValue(options.firstFrame); m_page->intEnd->setValue(options.lastFrame); m_page->sequenceStart->setValue(options.sequenceStart); m_page->intWidth->setValue(options.width); m_page->intHeight->setValue(options.height); m_page->intFramesPerSecond->setValue(options.frameRate); m_page->videoFilename->setStartDir(options.resolveAbsoluteDocumentFilePath(documentPath)); m_page->videoFilename->setFileName(options.videoFileName); m_page->dirRequester->setStartDir(options.resolveAbsoluteDocumentFilePath(documentPath)); m_page->dirRequester->setFileName(options.directory); } else { m_page->intStart->setValue(m_image->animationInterface()->playbackRange().start()); m_page->intEnd->setValue(m_image->animationInterface()->playbackRange().end()); m_page->sequenceStart->setValue(m_image->animationInterface()->playbackRange().start()); m_page->intWidth->setValue(m_image->width()); m_page->intHeight->setValue(m_image->height()); m_page->intFramesPerSecond->setValue(m_image->animationInterface()->framerate()); m_page->videoFilename->setStartDir(options.resolveAbsoluteDocumentFilePath(documentPath)); m_page->videoFilename->setFileName(defaultVideoFileName(m_doc, options.videoMimeType)); m_page->dirRequester->setStartDir(options.resolveAbsoluteDocumentFilePath(documentPath)); m_page->dirRequester->setFileName(options.directory); } for (int i = 0; i < m_page->cmbMimetype->count(); ++i) { if (m_page->cmbMimetype->itemData(i).toString() == options.frameMimeType) { m_page->cmbMimetype->setCurrentIndex(i); break; } } for (int i = 0; i < m_page->cmbRenderType->count(); ++i) { if (m_page->cmbRenderType->itemData(i).toString() == options.videoMimeType) { m_page->cmbRenderType->setCurrentIndex(i); break; } } m_page->chkIncludeAudio->setChecked(options.includeAudio); if (options.shouldDeleteSequence) { KIS_SAFE_ASSERT_RECOVER_NOOP(options.shouldEncodeVideo); m_page->shouldExportOnlyVideo->setChecked(true); } else if (!options.shouldEncodeVideo) { KIS_SAFE_ASSERT_RECOVER_NOOP(!options.shouldDeleteSequence); m_page->shouldExportOnlyImageSequence->setChecked(true); } else { m_page->shouldExportAll->setChecked(true); // export to both } { KisConfig cfg(true); KisPropertiesConfigurationSP settings = cfg.exportConfiguration("VIDEO_ENCODER"); getDefaultVideoEncoderOptions(options.videoMimeType, settings, &m_customFFMpegOptionsString, &m_forceHDRVideo); } m_page->ffmpegLocation->setStartDir(QFileInfo(m_doc->localFilePath()).path()); m_page->ffmpegLocation->setFileName(findFFMpeg(options.ffmpegPath)); } QString DlgAnimationRenderer::defaultVideoFileName(KisDocument *doc, const QString &mimeType) { const QString docFileName = !doc->localFilePath().isEmpty() ? doc->localFilePath() : i18n("Untitled"); return QString("%1.%2") .arg(QFileInfo(docFileName).completeBaseName()) .arg(KisMimeDatabase::suffixesForMimeType(mimeType).first()); } void DlgAnimationRenderer::selectRenderType(int index) { const QString mimeType = m_page->cmbRenderType->itemData(index).toString(); m_page->bnRenderOptions->setEnabled(mimeType != "image/gif"); m_page->lblGifWarning->setVisible((mimeType == "image/gif" && m_page->intFramesPerSecond->value() > 50)); QString videoFileName = defaultVideoFileName(m_doc, mimeType); if (!m_page->videoFilename->fileName().isEmpty()) { const QFileInfo info = QFileInfo(m_page->videoFilename->fileName()); const QString baseName = info.completeBaseName(); const QString path = info.path(); videoFileName = QString("%1%2%3.%4").arg(path).arg(QDir::separator()).arg(baseName).arg(KisMimeDatabase::suffixesForMimeType(mimeType).first()); } m_page->videoFilename->setMimeTypeFilters(QStringList() << mimeType, mimeType); m_page->videoFilename->setFileName(videoFileName); } void DlgAnimationRenderer::selectRenderOptions() { const int index = m_page->cmbRenderType->currentIndex(); const QString mimetype = m_page->cmbRenderType->itemData(index).toString(); const VideoExportOptionsDialog::ContainerType containerType = mimetype == "video/ogg" ? VideoExportOptionsDialog::OGV : VideoExportOptionsDialog::DEFAULT; VideoExportOptionsDialog *encoderConfigWidget = new VideoExportOptionsDialog(containerType, this); // we always enable HDR, letting the user to force it encoderConfigWidget->setSupportsHDR(true); { KisConfig cfg(true); KisPropertiesConfigurationSP settings = cfg.exportConfiguration("VIDEO_ENCODER"); encoderConfigWidget->setConfiguration(settings); } KoDialog dlg(this); dlg.setMainWidget(encoderConfigWidget); dlg.setButtons(KoDialog::Ok | KoDialog::Cancel); if (dlg.exec() == QDialog::Accepted) { KisConfig cfg(false); cfg.setExportConfiguration("VIDEO_ENCODER", encoderConfigWidget->configuration()); m_customFFMpegOptionsString = encoderConfigWidget->customUserOptionsString(); m_forceHDRVideo = encoderConfigWidget->forceHDRModeForFrames(); } dlg.setMainWidget(0); encoderConfigWidget->deleteLater(); } void DlgAnimationRenderer::sequenceMimeTypeOptionsClicked() { int index = m_page->cmbMimetype->currentIndex(); KisConfigWidget *frameExportConfigWidget = 0; QString mimetype = m_page->cmbMimetype->itemData(index).toString(); QSharedPointer filter(KisImportExportManager::filterForMimeType(mimetype, KisImportExportManager::Export)); if (filter) { frameExportConfigWidget = filter->createConfigurationWidget(0, KisDocument::nativeFormatMimeType(), mimetype.toLatin1()); if (frameExportConfigWidget) { KisPropertiesConfigurationSP config = filter->lastSavedConfiguration("", mimetype.toLatin1()); if (config) { KisImportExportManager::fillStaticExportConfigurationProperties(config, m_image); } frameExportConfigWidget->setConfiguration(config); KoDialog dlg(this); dlg.setMainWidget(frameExportConfigWidget); dlg.setButtons(KoDialog::Ok | KoDialog::Cancel); if (dlg.exec() == QDialog::Accepted) { KisConfig cfg(false); cfg.setExportConfiguration(mimetype, frameExportConfigWidget->configuration()); } frameExportConfigWidget->hide(); dlg.setMainWidget(0); frameExportConfigWidget->setParent(0); frameExportConfigWidget->deleteLater(); } } } inline int roundByTwo(int value) { return value + (value & 0x1); } KisAnimationRenderingOptions DlgAnimationRenderer::getEncoderOptions() const { KisAnimationRenderingOptions options; options.lastDocuemntPath = m_doc->localFilePath(); options.videoMimeType = m_page->cmbRenderType->currentData().toString(); options.frameMimeType = m_page->cmbMimetype->currentData().toString(); options.basename = m_page->txtBasename->text(); options.directory = m_page->dirRequester->fileName(); options.firstFrame = m_page->intStart->value(); options.lastFrame = m_page->intEnd->value(); options.sequenceStart = m_page->sequenceStart->value(); options.shouldEncodeVideo = !m_page->shouldExportOnlyImageSequence->isChecked(); options.shouldDeleteSequence = m_page->shouldExportOnlyVideo->isChecked(); options.includeAudio = m_page->chkIncludeAudio->isChecked(); options.ffmpegPath = m_page->ffmpegLocation->fileName(); options.frameRate = m_page->intFramesPerSecond->value(); if (options.frameRate > 50 && options.videoMimeType == "image/gif") { options.frameRate = 50; } options.width = roundByTwo(m_page->intWidth->value()); options.height = roundByTwo(m_page->intHeight->value()); options.videoFileName = m_page->videoFilename->fileName(); options.customFFMpegOptions = m_customFFMpegOptionsString; { KisConfig config(true); KisPropertiesConfigurationSP cfg = config.exportConfiguration(options.frameMimeType); if (cfg) { KisImportExportManager::fillStaticExportConfigurationProperties(cfg, m_image); } const bool forceHDR = m_forceHDRVideo && !m_page->shouldExportOnlyImageSequence->isChecked(); if (forceHDR) { KIS_SAFE_ASSERT_RECOVER_NOOP(options.frameMimeType == "image/png"); cfg->setProperty("forceSRGB", false); cfg->setProperty("saveAsHDR", true); } options.frameExportConfig = cfg; } return options; } void DlgAnimationRenderer::slotButtonClicked(int button) { if (button == KoDialog::Ok && !m_page->shouldExportOnlyImageSequence->isChecked()) { QString ffmpeg = m_page->ffmpegLocation->fileName(); if (m_page->videoFilename->fileName().isEmpty()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("Please enter a file name to render to.")); return; } else if (ffmpeg.isEmpty()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("The location of FFmpeg is unknown. Please install FFmpeg first: Krita cannot render animations without FFmpeg. (www.ffmpeg.org)")); return; } else { QFileInfo fi(ffmpeg); if (!fi.exists()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("The location of FFmpeg is invalid. Please select the correct location of the FFmpeg executable on your system.")); return; } } } KoDialog::slotButtonClicked(button); } void DlgAnimationRenderer::slotDialogAccepted() { KisConfig cfg(false); KisAnimationRenderingOptions options = getEncoderOptions(); cfg.setExportConfiguration("ANIMATION_EXPORT", options.toProperties()); } QString DlgAnimationRenderer::findFFMpeg(const QString &customLocation) { QString result; QStringList proposedPaths; if (!customLocation.isEmpty()) { proposedPaths << customLocation; proposedPaths << customLocation + QDir::separator() + "ffmpeg"; } proposedPaths << KoResourcePaths::getApplicationRoot() + QDir::separator() + "bin" + QDir::separator() + "ffmpeg"; #ifndef Q_OS_WIN proposedPaths << QDir::homePath() + "/bin/ffmpeg"; proposedPaths << "/usr/bin/ffmpeg"; proposedPaths << "/usr/local/bin/ffmpeg"; #endif Q_FOREACH (QString path, proposedPaths) { if (path.isEmpty()) continue; #ifdef Q_OS_WIN path = QDir::toNativeSeparators(QDir::cleanPath(path)); if (path.endsWith(QDir::separator())) { continue; } if (!path.endsWith(".exe")) { if (!QFile::exists(path)) { path += ".exe"; if (!QFile::exists(path)) { continue; } } } #endif QProcess testProcess; testProcess.start(path, QStringList() << "-version"); if (testProcess.waitForStarted(1000)) { testProcess.waitForFinished(1000); } const bool successfulStart = testProcess.state() == QProcess::NotRunning && testProcess.error() == QProcess::UnknownError; if (successfulStart) { result = path; break; } } return result; } void DlgAnimationRenderer::slotExportTypeChanged() { KisConfig cfg(false); bool willEncodeVideo = m_page->shouldExportAll->isChecked() || m_page->shouldExportOnlyVideo->isChecked(); // if a video format needs to be outputted if (willEncodeVideo) { // videos always uses PNG for creating video, so disable the ability to change the format m_page->cmbMimetype->setEnabled(false); for (int i = 0; i < m_page->cmbMimetype->count(); ++i) { if (m_page->cmbMimetype->itemData(i).toString() == "image/png") { m_page->cmbMimetype->setCurrentIndex(i); break; } } } m_page->intWidth->setVisible(willEncodeVideo); m_page->intHeight->setVisible(willEncodeVideo); m_page->intFramesPerSecond->setVisible(willEncodeVideo); m_page->fpsLabel->setVisible(willEncodeVideo); m_page->lblWidth->setVisible(willEncodeVideo); m_page->lblHeight->setVisible(willEncodeVideo); // if only exporting video if (m_page->shouldExportOnlyVideo->isChecked()) { m_page->cmbMimetype->setEnabled(false); // allow to change image format m_page->imageSequenceOptionsGroup->setVisible(false); m_page->videoOptionsGroup->setVisible(false); //shrinks the horizontal space temporarily to help resize() work m_page->videoOptionsGroup->setVisible(true); } // if only an image sequence needs to be output if (m_page->shouldExportOnlyImageSequence->isChecked()) { m_page->cmbMimetype->setEnabled(true); // allow to change image format m_page->videoOptionsGroup->setVisible(false); m_page->imageSequenceOptionsGroup->setVisible(false); m_page->imageSequenceOptionsGroup->setVisible(true); } // show all options if (m_page->shouldExportAll->isChecked() ) { m_page->imageSequenceOptionsGroup->setVisible(true); m_page->videoOptionsGroup->setVisible(true); } // for the resize to work as expected, try to hide elements first before displaying other ones. // if the widget gets bigger at any point, the resize will use that, even if elements are hidden later to make it smaller resize(m_page->sizeHint()); } void DlgAnimationRenderer::frameRateChanged(int framerate) { const QString mimeType = m_page->cmbRenderType->itemData(m_page->cmbRenderType->currentIndex()).toString(); m_page->lblGifWarning->setVisible((mimeType == "image/gif" && framerate > 50)); } void DlgAnimationRenderer::slotLockAspectRatioDimensionsWidth(int width) { Q_UNUSED(width); float aspectRatio = (float)m_image->width() / (float)m_image->height(); // update height here float newHeight = m_page->intWidth->value() / aspectRatio ; m_page->intHeight->setValue(newHeight); } void DlgAnimationRenderer::slotLockAspectRatioDimensionsHeight(int height) { Q_UNUSED(height); float aspectRatio = (float)m_image->width() / (float)m_image->height(); // update width here float newWidth = aspectRatio * m_page->intHeight->value(); m_page->intWidth->setValue(newWidth); } diff --git a/plugins/extensions/buginfo/CMakeLists.txt b/plugins/extensions/buginfo/CMakeLists.txt index a102ed6838..a236446476 100644 --- a/plugins/extensions/buginfo/CMakeLists.txt +++ b/plugins/extensions/buginfo/CMakeLists.txt @@ -1,10 +1,16 @@ -set(kritabuginfo_SOURCES - buginfo.cpp - dlg_buginfo.cpp +set(kritabuginfo_SOURCES + buginfo.cpp + dlg_buginfo.cpp + DlgSysInfo.cpp + DlgKritaLog.cpp ) ki18n_wrap_ui(kritabuginfo_SOURCES wdg_buginfo.ui ) + add_library(kritabuginfo MODULE ${kritabuginfo_SOURCES}) + target_link_libraries(kritabuginfo kritaui) + install(TARGETS kritabuginfo DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) + install( FILES buginfo.xmlgui DESTINATION ${DATA_INSTALL_DIR}/kritaplugins) diff --git a/plugins/extensions/buginfo/dlg_buginfo.h b/plugins/extensions/buginfo/DlgKritaLog.cpp similarity index 56% copy from plugins/extensions/buginfo/dlg_buginfo.h copy to plugins/extensions/buginfo/DlgKritaLog.cpp index 619a737b05..7af1b39431 100644 --- a/plugins/extensions/buginfo/dlg_buginfo.h +++ b/plugins/extensions/buginfo/DlgKritaLog.cpp @@ -1,47 +1,46 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef DLG_BUGINFO -#define DLG_BUGINFO +#include "DlgKritaLog.h" +#include -#include - -#include "ui_wdg_buginfo.h" +DlgKritaLog::DlgKritaLog(QWidget *parent) + : DlgBugInfo(parent) +{ + initialize(); +} +QString DlgKritaLog::originalFileName() +{ + return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log"; +} -class WdgBugInfo : public QWidget, public Ui::WdgBugInfo +QString DlgKritaLog::captionText() { - Q_OBJECT + return i18nc("Caption of the dialog with Krita usage log for bug reports", "Krita Usage Log: please paste this information to the bug report"); +} -public: - WdgBugInfo(QWidget *parent) : QWidget(parent) { - setupUi(this); - } -}; +QString DlgKritaLog::replacementWarningText() +{ + return "WARNING: The Krita usage log file doesn't exist."; +} -class DlgBugInfo: public KoDialog +DlgKritaLog::~DlgKritaLog() { - Q_OBJECT -public: - DlgBugInfo(QWidget * parent = 0); - ~DlgBugInfo() override; -private: - WdgBugInfo *m_page; -}; -#endif // DLG_BUGINFO +} diff --git a/plugins/extensions/buginfo/dlg_buginfo.h b/plugins/extensions/buginfo/DlgKritaLog.h similarity index 69% copy from plugins/extensions/buginfo/dlg_buginfo.h copy to plugins/extensions/buginfo/DlgKritaLog.h index 619a737b05..3294891a86 100644 --- a/plugins/extensions/buginfo/dlg_buginfo.h +++ b/plugins/extensions/buginfo/DlgKritaLog.h @@ -1,47 +1,49 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef DLG_BUGINFO -#define DLG_BUGINFO +#ifndef DLG_KRITA_LOG +#define DLG_KRITA_LOG #include +#include -#include "ui_wdg_buginfo.h" +class QSettings; - -class WdgBugInfo : public QWidget, public Ui::WdgBugInfo +class DlgKritaLog: public DlgBugInfo { Q_OBJECT - public: - WdgBugInfo(QWidget *parent) : QWidget(parent) { - setupUi(this); + DlgKritaLog(QWidget * parent = 0); + ~DlgKritaLog() override; + + + QString defaultNewFileName() override { + return "KritaUsageLog.txt"; } -}; -class DlgBugInfo: public KoDialog -{ - Q_OBJECT + QString originalFileName() override; + public: - DlgBugInfo(QWidget * parent = 0); - ~DlgBugInfo() override; + QString replacementWarningText() override; + QString captionText() override; + private: WdgBugInfo *m_page; }; #endif // DLG_BUGINFO diff --git a/plugins/extensions/buginfo/dlg_buginfo.h b/plugins/extensions/buginfo/DlgSysInfo.cpp similarity index 56% copy from plugins/extensions/buginfo/dlg_buginfo.h copy to plugins/extensions/buginfo/DlgSysInfo.cpp index 619a737b05..6f51270454 100644 --- a/plugins/extensions/buginfo/dlg_buginfo.h +++ b/plugins/extensions/buginfo/DlgSysInfo.cpp @@ -1,47 +1,45 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef DLG_BUGINFO -#define DLG_BUGINFO - -#include - -#include "ui_wdg_buginfo.h" +#include "DlgSysInfo.h" +#include +DlgSysInfo::DlgSysInfo(QWidget *parent) + : DlgBugInfo(parent) +{ + initialize(); +} -class WdgBugInfo : public QWidget, public Ui::WdgBugInfo +QString DlgSysInfo::originalFileName() { - Q_OBJECT + return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita-sysinfo.log"; +} -public: - WdgBugInfo(QWidget *parent) : QWidget(parent) { - setupUi(this); - } -}; +QString DlgSysInfo::captionText() +{ + return i18nc("Caption of the dialog with system information for bug reports", "Krita System Information: please paste this information to the bug report"); +} -class DlgBugInfo: public KoDialog +QString DlgSysInfo::replacementWarningText() { - Q_OBJECT -public: - DlgBugInfo(QWidget * parent = 0); - ~DlgBugInfo() override; -private: - WdgBugInfo *m_page; -}; + return "WARNING: The system information file doesn't exist."; +} -#endif // DLG_BUGINFO +DlgSysInfo::~DlgSysInfo() +{ +} diff --git a/plugins/extensions/buginfo/dlg_buginfo.h b/plugins/extensions/buginfo/DlgSysInfo.h similarity index 68% copy from plugins/extensions/buginfo/dlg_buginfo.h copy to plugins/extensions/buginfo/DlgSysInfo.h index 619a737b05..117d44578c 100644 --- a/plugins/extensions/buginfo/dlg_buginfo.h +++ b/plugins/extensions/buginfo/DlgSysInfo.h @@ -1,47 +1,49 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef DLG_BUGINFO -#define DLG_BUGINFO +#ifndef DLG_SYSINFO +#define DLG_SYSINFO #include +#include "dlg_buginfo.h" -#include "ui_wdg_buginfo.h" +class QSettings; -class WdgBugInfo : public QWidget, public Ui::WdgBugInfo +class DlgSysInfo: public DlgBugInfo { Q_OBJECT - public: - WdgBugInfo(QWidget *parent) : QWidget(parent) { - setupUi(this); + DlgSysInfo(QWidget * parent = 0); + ~DlgSysInfo() override; + + QString defaultNewFileName() override { + return "KritaSystemInformation.txt"; } -}; -class DlgBugInfo: public KoDialog -{ - Q_OBJECT + QString originalFileName() override; + public: - DlgBugInfo(QWidget * parent = 0); - ~DlgBugInfo() override; + QString replacementWarningText() override; + QString captionText() override; + private: WdgBugInfo *m_page; }; -#endif // DLG_BUGINFO +#endif // DLG_SYSINFO diff --git a/plugins/extensions/buginfo/buginfo.cpp b/plugins/extensions/buginfo/buginfo.cpp index c7ec1c84a7..2fe052e2a0 100644 --- a/plugins/extensions/buginfo/buginfo.cpp +++ b/plugins/extensions/buginfo/buginfo.cpp @@ -1,51 +1,65 @@ /* * Copyright (c) 2017 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 "buginfo.h" #include #include #include #include #include #include #include -#include "dlg_buginfo.h" +#include "DlgKritaLog.h" +#include "DlgSysInfo.h" + K_PLUGIN_FACTORY_WITH_JSON(BugInfoFactory, "kritabuginfo.json", registerPlugin();) + + BugInfo::BugInfo(QObject *parent, const QVariantList &) : KisActionPlugin(parent) { - KisAction *action = createAction("buginfo"); - connect(action, SIGNAL(triggered()), this, SLOT(slotBugInfo())); + KisAction *actionBug = createAction("buginfo"); + KisAction *actionSys = createAction("sysinfo"); + connect(actionBug, SIGNAL(triggered()), this, SLOT(slotKritaLog())); + connect(actionSys, SIGNAL(triggered()), this, SLOT(slotSysInfo())); + } BugInfo::~BugInfo() { } -void BugInfo::slotBugInfo() +void BugInfo::slotKritaLog() { - DlgBugInfo dlgBugInfo(viewManager()->mainWindow()); - dlgBugInfo.exec(); + DlgKritaLog dlgKritaLog(viewManager()->mainWindow()); + dlgKritaLog.exec(); } +void BugInfo::slotSysInfo() +{ + DlgSysInfo dlgSysInfo(viewManager()->mainWindow()); + dlgSysInfo.exec(); +} + + #include "buginfo.moc" diff --git a/plugins/extensions/buginfo/buginfo.h b/plugins/extensions/buginfo/buginfo.h index 21b24a90dc..48762ca3f2 100644 --- a/plugins/extensions/buginfo/buginfo.h +++ b/plugins/extensions/buginfo/buginfo.h @@ -1,40 +1,41 @@ /* * Copyright (c) 2017 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 BUGINFO_H #define BUGINFO_H #include #include class KUndo2MagicString; class BugInfo : public KisActionPlugin { Q_OBJECT public: BugInfo(QObject *parent, const QVariantList &); ~BugInfo() override; public Q_SLOTS: - void slotBugInfo(); + void slotKritaLog(); + void slotSysInfo(); }; #endif // BUGINFO_H diff --git a/plugins/extensions/buginfo/buginfo.xmlgui b/plugins/extensions/buginfo/buginfo.xmlgui index 1002c9d910..d8b769dd4c 100644 --- a/plugins/extensions/buginfo/buginfo.xmlgui +++ b/plugins/extensions/buginfo/buginfo.xmlgui @@ -1,8 +1,9 @@ - + + diff --git a/plugins/extensions/buginfo/dlg_buginfo.cpp b/plugins/extensions/buginfo/dlg_buginfo.cpp index 2a097360a2..31ecb24643 100644 --- a/plugins/extensions/buginfo/dlg_buginfo.cpp +++ b/plugins/extensions/buginfo/dlg_buginfo.cpp @@ -1,118 +1,186 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dlg_buginfo.h" #include #include #include #include #include #include #include #include #include #include #include #include #include +#include +#include -#include "kis_document_aware_spin_box_unit_manager.h" #include DlgBugInfo::DlgBugInfo(QWidget *parent) : KoDialog(parent) { setCaption(i18n("Please paste this information in your bug report")); - setButtons(User1 | Ok); + setButtons(User1 | User2 | Ok); setButtonText(User1, i18n("Copy to clipboard")); + setButtonText(User2, i18n("Save to file")); setDefaultButton(Ok); m_page = new WdgBugInfo(this); Q_CHECK_PTR(m_page); setMainWidget(m_page); - QString info; - - const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); - QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); + connect(this, &KoDialog::user1Clicked, this, [this](){ + QGuiApplication::clipboard()->setText(m_page->txtBugInfo->toPlainText()); + m_page->txtBugInfo->selectAll(); // feedback + }); - if (!kritarc.value("LogUsage", true).toBool() || !QFileInfo(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log").exists()) { + connect(this, &KoDialog::user2Clicked, this, &DlgBugInfo::saveToFile); - // NOTE: This is intentionally not translated! +} - // Krita version info - info.append("Krita"); - info.append("\n Version: ").append(KritaVersionWrapper::versionString(true)); - info.append("\n\n"); +void DlgBugInfo::initialize() +{ + initializeText(); + setCaption(captionText()); +} - info.append("Qt"); - info.append("\n Version (compiled): ").append(QT_VERSION_STR); - info.append("\n Version (loaded): ").append(qVersion()); - info.append("\n\n"); +void DlgBugInfo::initializeText() +{ + const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); + QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); - // OS information - info.append("OS Information"); - info.append("\n Build ABI: ").append(QSysInfo::buildAbi()); - info.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture()); - info.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture()); - info.append("\n Kernel Type: ").append(QSysInfo::kernelType()); - info.append("\n Kernel Version: ").append(QSysInfo::kernelVersion()); - info.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName()); - info.append("\n Product Type: ").append(QSysInfo::productType()); - info.append("\n Product Version: ").append(QSysInfo::productVersion()); - info.append("\n\n"); + QString info = infoText(kritarc); - // OpenGL information - info.append("\n").append(KisOpenGL::getDebugText()); - info.append("\n\n"); - // Hardware information - info.append("Hardware Information"); - info.append(QString("\n Memory: %1").arg(KisImageConfig(true).totalRAM() / 1024)).append(" Gb"); - info.append(QString("\n Cores: %1").arg(QThread::idealThreadCount())); - info.append("\n Swap: ").append(KisImageConfig(true).swapDir()); - } - else { - QFile f(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log"); - f.open(QFile::ReadOnly | QFile::Text); - info = QString::fromUtf8(f.readAll()); - f.close(); - } // calculate a default height for the widget int wheight = m_page->sizeHint().height(); m_page->txtBugInfo->setText(info); QFontMetrics fm = m_page->txtBugInfo->fontMetrics(); int target_height = fm.height() * info.split('\n').size() + wheight; QRect screen_rect = QGuiApplication::primaryScreen()->availableGeometry(); resize(m_page->size().width(), target_height > screen_rect.height() ? screen_rect.height() : target_height); +} + +void DlgBugInfo::saveToFile() +{ + KoFileDialog dlg(this, KoFileDialog::SaveFile, i18n("Save to file")); + dlg.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/" + defaultNewFileName()); + dlg.setMimeTypeFilters(QStringList("text/plain"), "text/plain"); + QString filename = dlg.filename(); + + if (filename.isEmpty()) { + return; + } else { + + QString originalLogFileName = originalFileName(); + if (!originalLogFileName.isEmpty() && QFileInfo(originalLogFileName).exists()) + { + QFile::copy(originalLogFileName, filename); + } else { + + QFile file(filename); + if (!file.open(QIODevice::WriteOnly)) { + QMessageBox::information(this, i18n("Unable to open file"), + file.errorString()); + return; + } + QTextStream out(&file); + out << m_page->txtBugInfo->toPlainText(); + file.close(); + } + } +} + +QString DlgBugInfo::basicSystemInformationReplacementText() +{ + QString info; + + // Krita version info + info.append("Krita"); + info.append("\n Version: ").append(KritaVersionWrapper::versionString(true)); + info.append("\n\n"); + + info.append("Qt"); + info.append("\n Version (compiled): ").append(QT_VERSION_STR); + info.append("\n Version (loaded): ").append(qVersion()); + info.append("\n\n"); + + // OS information + info.append("OS Information"); + info.append("\n Build ABI: ").append(QSysInfo::buildAbi()); + info.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture()); + info.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture()); + info.append("\n Kernel Type: ").append(QSysInfo::kernelType()); + info.append("\n Kernel Version: ").append(QSysInfo::kernelVersion()); + info.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName()); + info.append("\n Product Type: ").append(QSysInfo::productType()); + info.append("\n Product Version: ").append(QSysInfo::productVersion()); + info.append("\n\n"); + + // OpenGL information + info.append("\n").append(KisOpenGL::getDebugText()); + info.append("\n\n"); + // Hardware information + info.append("Hardware Information"); + info.append(QString("\n Memory: %1").arg(KisImageConfig(true).totalRAM() / 1024)).append(" Gb"); + info.append(QString("\n Cores: %1").arg(QThread::idealThreadCount())); + info.append("\n Swap: ").append(KisImageConfig(true).swapDir()); + + return info; +} + +QString DlgBugInfo::infoText(QSettings& kritarc) +{ + QString info; + + if (!kritarc.value("LogUsage", true).toBool() || !QFileInfo(originalFileName()).exists()) { + + // NOTE: This is intentionally not translated! + + info.append(replacementWarningText()); + info.append("File name and location: " + originalFileName()); + info.append("------------------------------------"); + info.append("\n\n"); + + info.append(basicSystemInformationReplacementText()); + } + else { + + QFile log(originalFileName()); + log.open(QFile::ReadOnly | QFile::Text); + info += QString::fromUtf8(log.readAll()); + log.close(); + } + + return info; - connect(this, &KoDialog::user1Clicked, this, [this](){ - QGuiApplication::clipboard()->setText(m_page->txtBugInfo->toPlainText()); - m_page->txtBugInfo->selectAll(); // feedback - }); } DlgBugInfo::~DlgBugInfo() { delete m_page; } diff --git a/plugins/extensions/buginfo/dlg_buginfo.h b/plugins/extensions/buginfo/dlg_buginfo.h index 619a737b05..3a758ce82c 100644 --- a/plugins/extensions/buginfo/dlg_buginfo.h +++ b/plugins/extensions/buginfo/dlg_buginfo.h @@ -1,47 +1,61 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DLG_BUGINFO #define DLG_BUGINFO #include #include "ui_wdg_buginfo.h" +class QSettings; class WdgBugInfo : public QWidget, public Ui::WdgBugInfo { Q_OBJECT public: WdgBugInfo(QWidget *parent) : QWidget(parent) { setupUi(this); } }; class DlgBugInfo: public KoDialog { Q_OBJECT public: DlgBugInfo(QWidget * parent = 0); ~DlgBugInfo() override; + + void initialize(); + void initializeText(); + void saveToFile(); + + virtual QString defaultNewFileName() = 0; + virtual QString originalFileName() = 0; + virtual QString captionText() = 0; + virtual QString replacementWarningText() = 0; + QString infoText(QSettings& kritarc); + + QString basicSystemInformationReplacementText(); + private: WdgBugInfo *m_page; }; #endif // DLG_BUGINFO diff --git a/plugins/extensions/imagesize/dlg_canvassize.cc b/plugins/extensions/imagesize/dlg_canvassize.cc index 662c2a897c..170251008d 100644 --- a/plugins/extensions/imagesize/dlg_canvassize.cc +++ b/plugins/extensions/imagesize/dlg_canvassize.cc @@ -1,498 +1,498 @@ /* * * Copyright (c) 2009 Edward Apap * Copyright (c) 2013 Juan Palacios * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dlg_canvassize.h" #include "kcanvaspreview.h" #include #include #include #include #include #include #include #include // used to extend KoUnit in comboboxes static const QString percentStr(i18n("Percent (%)")); const QString DlgCanvasSize::PARAM_PREFIX = "canvasizedlg"; const QString DlgCanvasSize::PARAM_WIDTH_UNIT = DlgCanvasSize::PARAM_PREFIX + "_widthunit"; const QString DlgCanvasSize::PARAM_HEIGHT_UNIT = DlgCanvasSize::PARAM_PREFIX + "_heightunit"; const QString DlgCanvasSize::PARAM_XOFFSET_UNIT = DlgCanvasSize::PARAM_PREFIX + "_xoffsetunit"; const QString DlgCanvasSize::PARAM_YOFFSET_UNIT = DlgCanvasSize::PARAM_PREFIX + "_yoffsetunit"; DlgCanvasSize::DlgCanvasSize(QWidget *parent, int width, int height, double resolution) : KoDialog(parent) , m_keepAspect(true) , m_aspectRatio((double)width / height) , m_resolution(resolution) , m_originalWidth(width) , m_originalHeight(height) , m_newWidth(width) , m_newHeight(height) , m_xOffset(0) , m_yOffset(0) { setCaption(i18n("Resize Canvas")); setButtons(Ok | Cancel); setDefaultButton(Ok); m_page = new WdgCanvasSize(this); Q_CHECK_PTR(m_page); m_page->layout()->setMargin(0); m_page->setObjectName("canvas_size"); _widthUnitManager = new KisDocumentAwareSpinBoxUnitManager(this); _heightUnitManager = new KisDocumentAwareSpinBoxUnitManager(this, KisDocumentAwareSpinBoxUnitManager::PIX_DIR_Y); KisConfig cfg(true); _widthUnitManager->setApparentUnitFromSymbol("px"); _heightUnitManager->setApparentUnitFromSymbol("px"); m_page->newWidthDouble->setUnitManager(_widthUnitManager); m_page->newHeightDouble->setUnitManager(_heightUnitManager); m_page->newWidthDouble->setDecimals(2); m_page->newHeightDouble->setDecimals(2); m_page->newWidthDouble->setDisplayUnit(false); m_page->newHeightDouble->setDisplayUnit(false); m_page->newWidthDouble->setValue(width); m_page->newWidthDouble->setFocus(); m_page->newHeightDouble->setValue(height); m_page->widthUnit->setModel(_widthUnitManager); m_page->heightUnit->setModel(_heightUnitManager); QString unitw = cfg.readEntry(PARAM_WIDTH_UNIT, "px"); QString unith = cfg.readEntry(PARAM_HEIGHT_UNIT, "px"); _widthUnitManager->setApparentUnitFromSymbol(unitw); _heightUnitManager->setApparentUnitFromSymbol(unith); const int wUnitIndex = _widthUnitManager->getsUnitSymbolList().indexOf(unitw); const int hUnitIndex = _widthUnitManager->getsUnitSymbolList().indexOf(unith); m_page->widthUnit->setCurrentIndex(wUnitIndex); m_page->heightUnit->setCurrentIndex(hUnitIndex); _xOffsetUnitManager = new KisDocumentAwareSpinBoxUnitManager(this); _yOffsetUnitManager = new KisDocumentAwareSpinBoxUnitManager(this, KisDocumentAwareSpinBoxUnitManager::PIX_DIR_Y); _xOffsetUnitManager->setApparentUnitFromSymbol("px"); _yOffsetUnitManager->setApparentUnitFromSymbol("px"); m_page->xOffsetDouble->setUnitManager(_xOffsetUnitManager); m_page->yOffsetDouble->setUnitManager(_yOffsetUnitManager); m_page->xOffsetDouble->setDecimals(2); m_page->yOffsetDouble->setDecimals(2); m_page->xOffsetDouble->setDisplayUnit(false); m_page->yOffsetDouble->setDisplayUnit(false); m_page->xOffUnit->setModel(_xOffsetUnitManager); m_page->yOffUnit->setModel(_yOffsetUnitManager); m_page->xOffsetDouble->changeValue(m_xOffset); m_page->yOffsetDouble->changeValue(m_yOffset); QString unitx = cfg.readEntry(PARAM_XOFFSET_UNIT, "px"); QString unity = cfg.readEntry(PARAM_YOFFSET_UNIT, "px"); _xOffsetUnitManager->setApparentUnitFromSymbol(unitx); _yOffsetUnitManager->setApparentUnitFromSymbol(unity); const int xUnitIndex = _xOffsetUnitManager->getsUnitSymbolList().indexOf(unitx); const int yUnitIndex = _yOffsetUnitManager->getsUnitSymbolList().indexOf(unity); m_page->xOffUnit->setCurrentIndex(xUnitIndex); m_page->yOffUnit->setCurrentIndex(yUnitIndex); m_page->canvasPreview->setImageSize(m_originalWidth, m_originalHeight); m_page->canvasPreview->setCanvasSize(m_originalWidth, m_originalHeight); m_page->canvasPreview->setImageOffset(m_xOffset, m_yOffset); m_page->aspectRatioBtn->setKeepAspectRatio(cfg.readEntry("CanvasSize/KeepAspectRatio", false)); m_page->constrainProportionsCkb->setChecked(cfg.readEntry("CanvasSize/ConstrainProportions", false)); m_keepAspect = cfg.readEntry("CanvasSize/KeepAspectRatio", false); m_group = new QButtonGroup(m_page); m_group->addButton(m_page->topLeft, NORTH_WEST); m_group->addButton(m_page->topCenter, NORTH); m_group->addButton(m_page->topRight, NORTH_EAST); m_group->addButton(m_page->middleLeft, WEST); m_group->addButton(m_page->middleCenter, CENTER); m_group->addButton(m_page->middleRight, EAST); m_group->addButton(m_page->bottomLeft, SOUTH_WEST); m_group->addButton(m_page->bottomCenter, SOUTH); m_group->addButton(m_page->bottomRight, SOUTH_EAST); loadAnchorIcons(); m_group->button(CENTER)->setChecked(true); updateAnchorIcons(CENTER); KisSizeGroup *labelsGroup = new KisSizeGroup(this); labelsGroup->addWidget(m_page->lblNewWidth); labelsGroup->addWidget(m_page->lblNewHeight); labelsGroup->addWidget(m_page->lblXOff); labelsGroup->addWidget(m_page->lblYOff); labelsGroup->addWidget(m_page->lblAnchor); KisSizeGroup *spinboxesGroup = new KisSizeGroup(this); spinboxesGroup->addWidget(m_page->newWidthDouble); spinboxesGroup->addWidget(m_page->newHeightDouble); spinboxesGroup->addWidget(m_page->xOffsetDouble); spinboxesGroup->addWidget(m_page->yOffsetDouble); KisSizeGroup *comboboxesGroup = new KisSizeGroup(this); comboboxesGroup->addWidget(m_page->widthUnit); comboboxesGroup->addWidget(m_page->heightUnit); comboboxesGroup->addWidget(m_page->xOffUnit); comboboxesGroup->addWidget(m_page->yOffUnit); setMainWidget(m_page); connect(this, SIGNAL(okClicked()), this, SLOT(accept())); connect(m_page->newWidthDouble, SIGNAL(valueChangedPt(double)), this, SLOT(slotWidthChanged(double))); connect(m_page->newHeightDouble, SIGNAL(valueChangedPt(double)), this, SLOT(slotHeightChanged(double))); connect(m_page->widthUnit, SIGNAL(currentIndexChanged(int)), _widthUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(m_page->heightUnit, SIGNAL(currentIndexChanged(int)), _heightUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(_widthUnitManager, SIGNAL(unitChanged(int)), m_page->widthUnit, SLOT(setCurrentIndex(int))); connect(_heightUnitManager, SIGNAL(unitChanged(int)), m_page->heightUnit, SLOT(setCurrentIndex(int))); connect(m_page->xOffsetDouble, SIGNAL(valueChangedPt(double)), this, SLOT(slotXOffsetChanged(double))); connect(m_page->yOffsetDouble, SIGNAL(valueChangedPt(double)), this, SLOT(slotYOffsetChanged(double))); connect(m_page->xOffUnit, SIGNAL(currentIndexChanged(int)), _xOffsetUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(m_page->yOffUnit, SIGNAL(currentIndexChanged(int)), _yOffsetUnitManager, SLOT(selectApparentUnitFromIndex(int))); connect(_xOffsetUnitManager, SIGNAL(unitChanged(int)), m_page->xOffUnit, SLOT(setCurrentIndex(int))); connect(_yOffsetUnitManager, SIGNAL(unitChanged(int)), m_page->yOffUnit, SLOT(setCurrentIndex(int))); connect(m_page->constrainProportionsCkb, SIGNAL(toggled(bool)), this, SLOT(slotAspectChanged(bool))); connect(m_page->aspectRatioBtn, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(slotAspectChanged(bool))); connect(m_group, SIGNAL(buttonClicked(int)), SLOT(slotAnchorButtonClicked(int))); connect(m_page->canvasPreview, SIGNAL(sigModifiedXOffset(int)), this, SLOT(slotCanvasPreviewXOffsetChanged(int))); connect(m_page->canvasPreview, SIGNAL(sigModifiedYOffset(int)), this, SLOT(slotCanvasPreviewYOffsetChanged(int))); } DlgCanvasSize::~DlgCanvasSize() { KisConfig cfg(false); cfg.writeEntry("CanvasSize/KeepAspectRatio", m_page->aspectRatioBtn->keepAspectRatio()); cfg.writeEntry("CanvasSize/ConstrainProportions", m_page->constrainProportionsCkb->isChecked()); cfg.writeEntry(PARAM_WIDTH_UNIT, _widthUnitManager->getApparentUnitSymbol()); cfg.writeEntry(PARAM_HEIGHT_UNIT, _heightUnitManager->getApparentUnitSymbol()); cfg.writeEntry(PARAM_XOFFSET_UNIT, _xOffsetUnitManager->getApparentUnitSymbol()); cfg.writeEntry(PARAM_YOFFSET_UNIT, _yOffsetUnitManager->getApparentUnitSymbol()); delete m_page; } qint32 DlgCanvasSize::width() { return (qint32) m_newWidth; } qint32 DlgCanvasSize::height() { return (qint32) m_newHeight; } qint32 DlgCanvasSize::xOffset() { return (qint32) m_xOffset; } qint32 DlgCanvasSize::yOffset() { return (qint32) m_yOffset; } void DlgCanvasSize::slotAspectChanged(bool keep) { m_page->aspectRatioBtn->blockSignals(true); m_page->constrainProportionsCkb->blockSignals(true); m_page->aspectRatioBtn->setKeepAspectRatio(keep); m_page->constrainProportionsCkb->setChecked(keep); m_page->aspectRatioBtn->blockSignals(false); m_page->constrainProportionsCkb->blockSignals(false); m_keepAspect = keep; if (keep) { // size values may be out of sync, so we need to reset it to defaults m_newWidth = m_originalWidth; m_newHeight = m_originalHeight; m_xOffset = 0; m_yOffset = 0; m_page->canvasPreview->blockSignals(true); m_page->canvasPreview->setCanvasSize(m_newWidth, m_newHeight); m_page->canvasPreview->setImageOffset(m_xOffset, m_yOffset); m_page->canvasPreview->blockSignals(false); updateOffset(CENTER); updateButtons(CENTER); } } void DlgCanvasSize::slotAnchorButtonClicked(int id) { updateOffset(id); updateButtons(id); } void DlgCanvasSize::slotWidthChanged(double v) { //this slot receiv values in pt from the unitspinbox, so just use the resolution. const double resValue = v*_widthUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); m_newWidth = qRound(resValue); if (m_keepAspect) { m_newHeight = qRound(m_newWidth / m_aspectRatio); m_page->newHeightDouble->blockSignals(true); m_page->newHeightDouble->changeValue(v / m_aspectRatio); m_page->newHeightDouble->blockSignals(false); } int savedId = m_group->checkedId(); m_page->canvasPreview->blockSignals(true); m_page->canvasPreview->setCanvasSize(m_newWidth, m_newHeight); m_page->canvasPreview->blockSignals(false); updateOffset(savedId); updateButtons(savedId); } void DlgCanvasSize::slotHeightChanged(double v) { //this slot receiv values in pt from the unitspinbox, so just use the resolution. const double resValue = v*_heightUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); m_newHeight = qRound(resValue); if (m_keepAspect) { m_newWidth = qRound(m_newHeight * m_aspectRatio); m_page->newWidthDouble->blockSignals(true); m_page->newWidthDouble->changeValue(v * m_aspectRatio); m_page->newWidthDouble->blockSignals(false); } int savedId = m_group->checkedId(); m_page->canvasPreview->blockSignals(true); m_page->canvasPreview->setCanvasSize(m_newWidth, m_newHeight); m_page->canvasPreview->blockSignals(false); updateOffset(savedId); updateButtons(savedId); } void DlgCanvasSize::slotXOffsetChanged(double v) { //this slot receiv values in pt from the unitspinbox, so just use the resolution. const double resValue = v*_xOffsetUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); m_xOffset = qRound(resValue); m_page->canvasPreview->blockSignals(true); m_page->canvasPreview->setImageOffset(m_xOffset, m_yOffset); m_page->canvasPreview->blockSignals(false); updateButtons(-1); } void DlgCanvasSize::slotYOffsetChanged(double v) { //this slot receiv values in pt from the unitspinbox, so just use the resolution. const double resValue = v*_xOffsetUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); m_yOffset = qRound(resValue); m_page->canvasPreview->blockSignals(true); m_page->canvasPreview->setImageOffset(m_xOffset, m_yOffset); m_page->canvasPreview->blockSignals(false); updateButtons(-1); } void DlgCanvasSize::slotCanvasPreviewXOffsetChanged(int v) { double newVal = v / _xOffsetUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); m_page->xOffsetDouble->changeValue(newVal); } void DlgCanvasSize::slotCanvasPreviewYOffsetChanged(int v) { double newVal = v / _yOffsetUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); m_page->yOffsetDouble->changeValue(newVal); } void DlgCanvasSize::loadAnchorIcons() { m_anchorIcons[NORTH_WEST] = KisIconUtils::loadIcon("arrow-topleft"); m_anchorIcons[NORTH] = KisIconUtils::loadIcon("arrow-up"); m_anchorIcons[NORTH_EAST] = KisIconUtils::loadIcon("arrow-topright"); m_anchorIcons[EAST] = KisIconUtils::loadIcon("arrow-right"); m_anchorIcons[CENTER] = KisIconUtils::loadIcon("arrow_center"); m_anchorIcons[WEST] = KisIconUtils::loadIcon("arrow-left"); m_anchorIcons[SOUTH_WEST] = KisIconUtils::loadIcon("arrow-downleft"); m_anchorIcons[SOUTH] = KisIconUtils::loadIcon("arrow-down"); m_anchorIcons[SOUTH_EAST] = KisIconUtils::loadIcon("arrow-downright"); } void DlgCanvasSize::updateAnchorIcons(int id) { anchor iconLayout[10][9] = { {NONE, EAST, NONE, SOUTH, SOUTH_EAST, NONE, NONE, NONE, NONE}, {WEST, NONE, EAST, SOUTH_WEST, SOUTH, SOUTH_EAST, NONE, NONE, NONE}, {NONE, WEST, NONE, NONE, SOUTH_WEST, SOUTH, NONE, NONE, NONE}, {NORTH, NORTH_EAST, NONE, NONE, EAST, NONE, SOUTH, SOUTH_EAST, NONE}, {NORTH_WEST, NORTH, NORTH_EAST, WEST, NONE, EAST, SOUTH_WEST, SOUTH, SOUTH_EAST}, {NONE, NORTH_WEST, NORTH, NONE, WEST, NONE, NONE, SOUTH_WEST, SOUTH}, {NONE, NONE, NONE, NORTH, NORTH_EAST, NONE, NONE, EAST, NONE}, {NONE, NONE, NONE, NORTH_WEST, NORTH, NORTH_EAST, WEST, NONE, EAST}, {NONE, NONE, NONE, NONE, NORTH_WEST, NORTH, NONE, WEST, NONE}, {NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE} }; if (id == -1) { id = SOUTH_EAST + 1; } // we are going to swap arrows direction based on width and height shrinking bool shrinkWidth = (m_newWidth < m_originalWidth) ? true : false; bool shrinkHeight = (m_newHeight < m_originalHeight) ? true : false; for (int i = NORTH_WEST; i <= SOUTH_EAST; i++) { anchor iconId = iconLayout[id][i]; // all corner arrows represents shrinking in some direction if (shrinkWidth || shrinkHeight) { switch (iconId) { case NORTH_WEST: iconId = SOUTH_EAST; break; case NORTH_EAST: iconId = SOUTH_WEST; break; case SOUTH_WEST: iconId = NORTH_EAST; break; case SOUTH_EAST: iconId = NORTH_WEST; break; default: break; } } if (shrinkWidth) { switch (iconId) { case WEST: iconId = EAST; break; case EAST: iconId = WEST; break; default: break; } } if (shrinkHeight) { switch (iconId) { case NORTH: iconId = SOUTH; break; case SOUTH: iconId = NORTH; break; default: break; } } QAbstractButton *button = m_group->button(i); if (iconId == NONE) { button->setIcon(QIcon()); } else { button->setIcon(m_anchorIcons[iconId]); } } } void DlgCanvasSize::updateButtons(int forceId) { int id = m_group->checkedId(); if (forceId != -1) { m_group->setExclusive(true); m_group->button(forceId)->setChecked(true); updateAnchorIcons(forceId); } else if (id != -1) { double xOffset, yOffset; expectedOffset(id, xOffset, yOffset); // convert values to internal unit int internalXOffset = 0; int internalYOffset = 0; if (m_page->xOffUnit->currentText() == percentStr) { internalXOffset = qRound((xOffset * m_newWidth) / 100.0); internalYOffset = qRound((yOffset * m_newHeight) / 100.0); } else { const KoUnit xOffsetUnit = KoUnit::fromListForUi(m_page->xOffUnit->currentIndex()); internalXOffset = qRound(xOffsetUnit.fromUserValue(xOffset)); const KoUnit yOffsetUnit = KoUnit::fromListForUi(m_page->yOffUnit->currentIndex()); internalYOffset = qRound(yOffsetUnit.fromUserValue(yOffset)); } bool offsetAsExpected = internalXOffset == m_xOffset && internalYOffset == m_yOffset; if (offsetAsExpected) { m_group->setExclusive(true); } else { m_group->setExclusive(false); m_group->button(id)->setChecked(false); id = -1; } updateAnchorIcons(id); } else { updateAnchorIcons(id); } } void DlgCanvasSize::updateOffset(int id) { if (id == -1) return; double xOffset; double yOffset; expectedOffset(id, xOffset, yOffset); m_page->xOffsetDouble->changeValue(xOffset); m_page->yOffsetDouble->changeValue(yOffset); } void DlgCanvasSize::expectedOffset(int id, double &xOffset, double &yOffset) { const double xCoeff = (id % 3) * 0.5; - const double yCoeff = (id / 3.0) * 0.5; + const double yCoeff = (int)(id / 3.0) * 0.5; const int xDiff = m_newWidth - m_originalWidth; const int yDiff = m_newHeight - m_originalHeight; //convert to unitmanager default unit. xOffset = xDiff * xCoeff / _xOffsetUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); yOffset = yDiff * yCoeff / _yOffsetUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px"); } diff --git a/plugins/extensions/offsetimage/wdg_offsetimage.ui b/plugins/extensions/offsetimage/wdg_offsetimage.ui index 8a1eedac33..6f64221f09 100644 --- a/plugins/extensions/offsetimage/wdg_offsetimage.ui +++ b/plugins/extensions/offsetimage/wdg_offsetimage.ui @@ -1,132 +1,138 @@ WdgOffsetImage 0 0 214 157 Rotate Image Offset 0 0 0 0 X: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 + + 100000.000000000000000 + Y: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 + + 100000.000000000000000 + 0 0 Offset by x/2, y/2 Qt::Vertical QSizePolicy::Minimum 0 0 KisDoubleParseUnitSpinBox QDoubleSpinBox
    kis_double_parse_unit_spin_box.h
    diff --git a/plugins/extensions/pykrita/plugin/PythonPluginManager.cpp b/plugins/extensions/pykrita/plugin/PythonPluginManager.cpp index 6db844de55..268c3f9880 100644 --- a/plugins/extensions/pykrita/plugin/PythonPluginManager.cpp +++ b/plugins/extensions/pykrita/plugin/PythonPluginManager.cpp @@ -1,418 +1,418 @@ /* * This file is part of PyKrita, Krita' Python scripting plugin. * * Copyright (C) 2013 Alex Turbov * Copyright (C) 2014-2016 Boudewijn Rempt * Copyright (C) 2017 Jouni Pentikäinen (joupent@gmail.com) * * 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 "PythonPluginManager.h" #include #include #include #include #include #include #include #include #include "config.h" #include "version_checker.h" PythonPluginManager* instance = 0; // PythonPlugin implementation QString PythonPlugin::moduleFilePathPart() const { QString filePath = m_moduleName; return filePath.replace(".", "/"); } bool PythonPlugin::isValid() const { dbgScript << "Got Krita/PythonPlugin: " << name() << ", module-path=" << moduleName() ; // Make sure mandatory properties are here if (m_name.isEmpty()) { dbgScript << "Ignore desktop file w/o a name"; return false; } if (m_moduleName.isEmpty()) { dbgScript << "Ignore desktop file w/o a module to import"; return false; } #if PY_MAJOR_VERSION == 2 // Check if the plug-in is compatible with Python 2 or not. if (m_properties["X-Python-2-Compatible"].toBool() != true) { dbgScript << "Ignoring plug-in. It is marked incompatible with Python 2."; return false; } #endif return true; } // PythonPluginManager implementation PythonPluginManager::PythonPluginManager() : QObject(0) , m_model(0, this) {} const QList& PythonPluginManager::plugins() const { return m_plugins; } PythonPlugin * PythonPluginManager::plugin(int index) { if (index >= 0 && index < m_plugins.count()) { return &m_plugins[index]; } return nullptr; } PythonPluginsModel * PythonPluginManager::model() { return &m_model; } void PythonPluginManager::unloadAllModules() { Q_FOREACH(PythonPlugin plugin, m_plugins) { if (plugin.m_loaded) { unloadModule(plugin); } } } bool PythonPluginManager::verifyModuleExists(PythonPlugin &plugin) { // Find the module: // 0) try to locate directory based plugin first QString rel_path = plugin.moduleFilePathPart(); rel_path = rel_path + "/" + "__init__.py"; dbgScript << "Finding Python module with rel_path:" << rel_path; QString module_path = KoResourcePaths::findResource("pythonscripts", rel_path); dbgScript << "module_path:" << module_path; if (module_path.isEmpty()) { // 1) Nothing found, then try file based plugin rel_path = plugin.moduleFilePathPart() + ".py"; dbgScript << "Finding Python module with rel_path:" << rel_path; module_path = KoResourcePaths::findResource("pythonscripts", rel_path); dbgScript << "module_path:" << module_path; } // Is anything found at all? if (module_path.isEmpty()) { plugin.m_broken = true; plugin.m_errorReason = i18nc( "@info:tooltip" , "Unable to find the module specified %1" , plugin.moduleName() ); dbgScript << "Cannot load module:" << plugin.m_errorReason; return false; } dbgScript << "Found module path:" << module_path; return true; } QPair PythonPluginManager::parseDependency(const QString& d) { // Check if dependency has package info attached const int pnfo = d.indexOf('('); if (pnfo != -1) { QString dependency = d.mid(0, pnfo); QString version_str = d.mid(pnfo + 1, d.size() - pnfo - 2).trimmed(); dbgScript << "Desired version spec [" << dependency << "]:" << version_str; PyKrita::version_checker checker = PyKrita::version_checker::fromString(version_str); if (!(checker.isValid() && d.endsWith(')'))) { dbgScript << "Invalid version spec " << d; QString reason = i18nc( "@info:tooltip" , "

    Specified version has invalid format for dependency %1: " "%2. Skipped

    " , dependency , version_str ); return qMakePair(reason, PyKrita::version_checker()); } return qMakePair(dependency, checker); } return qMakePair(d, PyKrita::version_checker(PyKrita::version_checker::undefined)); } /** * Collect dependencies and check them. To do it * just try to import a module... when unload it ;) * * \c X-Python-Dependencies property of \c .desktop file has the following format: * python-module(version-info), where python-module * a python module name to be imported, version-spec * is a version triplet delimited by dots, possible w/ leading compare * operator: \c =, \c <, \c >, \c <=, \c >= */ void PythonPluginManager::verifyDependenciesSetStatus(PythonPlugin& plugin) { QStringList dependencies = plugin.property("X-Python-Dependencies").toStringList(); PyKrita::Python py = PyKrita::Python(); QString reason = i18nc("@info:tooltip", "Dependency check"); Q_FOREACH(const QString & d, dependencies) { QPair info_pair = parseDependency(d); PyKrita::version_checker& checker = info_pair.second; if (!checker.isValid()) { plugin.m_broken = true; reason += info_pair.first; continue; } dbgScript << "Try to import dependency module/package:" << d; // Try to import a module const QString& dependency = info_pair.first; PyObject* module = py.moduleImport(PQ(dependency)); if (module) { if (checker.isEmpty()) { // Need to check smth? dbgScript << "No version to check, just make sure it's loaded:" << dependency; Py_DECREF(module); continue; } // Try to get __version__ from module - // See PEP396: http://www.python.org/dev/peps/pep-0396/ + // See PEP396: https://www.python.org/dev/peps/pep-0396/ PyObject* version_obj = py.itemString("__version__", PQ(dependency)); if (!version_obj) { dbgScript << "No __version__ for " << dependency << "[" << plugin.name() << "]:\n" << py.lastTraceback() ; plugin.m_unstable = true; reason += i18nc( "@info:tooltip" , "

    Failed to check version of dependency %1: " "Module do not have PEP396 __version__ attribute. " "It is not disabled, but behaviour is unpredictable...

    " , dependency ); } PyKrita::version dep_version = PyKrita::version::fromPythonObject(version_obj); if (!dep_version.isValid()) { // Dunno what is this... Giving up! dbgScript << "***: Can't parse module version for" << dependency; plugin.m_unstable = true; reason += i18nc( "@info:tooltip" , "

    %1: Unexpected module's version format" , dependency ); } else if (!checker(dep_version)) { dbgScript << "Version requirement check failed [" << plugin.name() << "] for " << dependency << ": wanted " << checker.operationToString() << QString(checker.required()) << ", but found" << QString(dep_version) ; plugin.m_broken = true; reason += i18nc( "@info:tooltip" , "

    %1: No suitable version found. " "Required version %2 %3, but found %4

    " , dependency , checker.operationToString() , QString(checker.required()) , QString(dep_version) ); } // Do not need this module anymore... Py_DECREF(module); } else { dbgScript << "Load failure [" << plugin.name() << "]:\n" << py.lastTraceback(); plugin.m_broken = true; reason += i18nc( "@info:tooltip" , "

    Failure on module load %1:

    %2
    " , dependency , py.lastTraceback() ); } } if (plugin.isBroken() || plugin.isUnstable()) { plugin.m_errorReason = reason; } } void PythonPluginManager::scanPlugins() { m_plugins.clear(); KConfigGroup pluginSettings(KSharedConfig::openConfig(), "python"); QStringList desktopFiles = KoResourcePaths::findAllResources("data", "pykrita/*desktop"); Q_FOREACH(const QString &desktopFile, desktopFiles) { const KDesktopFile df(desktopFile); const KConfigGroup dg = df.desktopGroup(); if (dg.readEntry("ServiceTypes") == "Krita/PythonPlugin") { PythonPlugin plugin; plugin.m_comment = df.readComment(); plugin.m_name = df.readName(); plugin.m_moduleName = dg.readEntry("X-KDE-Library"); plugin.m_properties["X-Python-2-Compatible"] = dg.readEntry("X-Python-2-Compatible", false); QString manual = dg.readEntry("X-Krita-Manual"); if (!manual.isEmpty()) { QFile f(QFileInfo(desktopFile).path() + "/" + plugin.m_moduleName + "/" + manual); if (f.exists()) { f.open(QFile::ReadOnly); QByteArray ba = f.readAll(); f.close(); plugin.m_manual = QString::fromUtf8(ba); } } if (!plugin.isValid()) { dbgScript << plugin.name() << "is not usable"; continue; } if (!verifyModuleExists(plugin)) { dbgScript << "Cannot load" << plugin.name() << ": broken" << plugin.isBroken() << "because:" << plugin.errorReason(); continue; } verifyDependenciesSetStatus(plugin); plugin.m_enabled = pluginSettings.readEntry(QString("enable_") + plugin.moduleName(), false); m_plugins.append(plugin); } } } void PythonPluginManager::tryLoadEnabledPlugins() { for (PythonPlugin &plugin : m_plugins) { dbgScript << "Trying to load plugin" << plugin.moduleName() << ". Enabled:" << plugin.isEnabled() << ". Broken: " << plugin.isBroken(); if (plugin.m_enabled && !plugin.isBroken()) { loadModule(plugin); } } } void PythonPluginManager::loadModule(PythonPlugin &plugin) { KIS_SAFE_ASSERT_RECOVER_RETURN(plugin.isEnabled() && !plugin.isBroken()); QString module_name = plugin.moduleName(); dbgScript << "Loading module: " << module_name; PyKrita::Python py = PyKrita::Python(); // Get 'plugins' key from 'pykrita' module dictionary. // Every entry has a module name as a key and 2 elements tuple as a value PyObject* plugins = py.itemString("plugins"); KIS_SAFE_ASSERT_RECOVER_RETURN(plugins); PyObject* module = py.moduleImport(PQ(module_name)); if (module) { // Move just loaded module to the dict const int ins_result = PyDict_SetItemString(plugins, PQ(module_name), module); KIS_SAFE_ASSERT_RECOVER_NOOP(ins_result == 0); Py_DECREF(module); // Handle failure in release mode. if (ins_result == 0) { // Initialize the module from Python's side PyObject* const args = Py_BuildValue("(s)", PQ(module_name)); PyObject* result = py.functionCall("_pluginLoaded", PyKrita::Python::PYKRITA_ENGINE, args); Py_DECREF(args); if (result) { dbgScript << "\t" << "success!"; plugin.m_loaded = true; return; } } plugin.m_errorReason = i18nc("@info:tooltip", "Internal engine failure"); } else { plugin.m_errorReason = i18nc( "@info:tooltip" , "Module not loaded:
    %1" , py.lastTraceback().replace("\n", "
    ") ); } plugin.m_broken = true; warnScript << "Error loading plugin" << module_name; } void PythonPluginManager::unloadModule(PythonPlugin &plugin) { KIS_SAFE_ASSERT_RECOVER_RETURN(plugin.m_loaded); KIS_SAFE_ASSERT_RECOVER_RETURN(!plugin.isBroken()); dbgScript << "Unloading module: " << plugin.moduleName(); PyKrita::Python py = PyKrita::Python(); // Get 'plugins' key from 'pykrita' module dictionary PyObject* plugins = py.itemString("plugins"); KIS_SAFE_ASSERT_RECOVER_RETURN(plugins); PyObject* const args = Py_BuildValue("(s)", PQ(plugin.moduleName())); py.functionCall("_pluginUnloading", PyKrita::Python::PYKRITA_ENGINE, args); Py_DECREF(args); // This will just decrement a reference count for module instance PyDict_DelItemString(plugins, PQ(plugin.moduleName())); // Remove the module also from 'sys.modules' dict to really unload it, // so if reloaded all @init actions will work again! PyObject* sys_modules = py.itemString("modules", "sys"); KIS_SAFE_ASSERT_RECOVER_RETURN(sys_modules); PyDict_DelItemString(sys_modules, PQ(plugin.moduleName())); plugin.m_loaded = false; } void PythonPluginManager::setPluginEnabled(PythonPlugin &plugin, bool enabled) { bool wasEnabled = plugin.isEnabled(); if (wasEnabled && !enabled) { unloadModule(plugin); } plugin.m_enabled = enabled; KConfigGroup pluginSettings(KSharedConfig::openConfig(), "python"); pluginSettings.writeEntry(QString("enable_") + plugin.moduleName(), enabled); if (!wasEnabled && enabled) { loadModule(plugin); } } diff --git a/plugins/extensions/pykrita/plugin/manager.ui b/plugins/extensions/pykrita/plugin/manager.ui index bf61894100..378b505727 100644 --- a/plugins/extensions/pykrita/plugin/manager.ui +++ b/plugins/extensions/pykrita/plugin/manager.ui @@ -1,59 +1,72 @@ ManagerPage 0 0 361 228 + + + + + + + + 75 + true + + + + Error: The Python engine could not be initialized + + + QAbstractItemView::SingleSelection QAbstractItemView::SelectRows false false false false false - - - - - 75 - true - - + + - Error: The Python engine could not be initialized + <html><head/><body><p><span style=" font-weight:600;">Note:</span> you need to restart Krita to enable or disable plugins.</p></body></html> + + + Qt::RichText + + + true - - - diff --git a/plugins/extensions/pykrita/plugin/utilities.cpp b/plugins/extensions/pykrita/plugin/utilities.cpp index de5c6c9123..dd599c1b44 100644 --- a/plugins/extensions/pykrita/plugin/utilities.cpp +++ b/plugins/extensions/pykrita/plugin/utilities.cpp @@ -1,706 +1,706 @@ // This file is part of PyKrita, Krita' Python scripting plugin. // // Copyright (C) 2006 Paul Giannaros // Copyright (C) 2012, 2013 Shaheed Haque // // 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) version 3, or any // later version accepted by the membership of KDE e.V. (or its // successor approved by the membership of KDE e.V.), which shall // act as a proxy defined in Section 6 of version 3 of the license. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library. If not, see . // // config.h defines PYKRITA_PYTHON_LIBRARY, the path to libpython.so // on the build system #include "config.h" #include "utilities.h" #include "PythonPluginManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "PykritaModule.h" #define THREADED 1 namespace PyKrita { static InitResult initStatus = INIT_UNINITIALIZED; static QScopedPointer pluginManagerInstance; InitResult initialize() { // Already initialized? if (initStatus == INIT_OK) return INIT_OK; dbgScript << "Initializing Python plugin for Python" << PY_MAJOR_VERSION << "," << PY_MINOR_VERSION; if (!Python::libraryLoad()) { return INIT_CANNOT_LOAD_PYTHON_LIBRARY; } // Update PYTHONPATH // 0) custom plugin directories (prefer local dir over systems') // 1) shipped krita module's dir QStringList pluginDirectories = KoResourcePaths::findDirs("pythonscripts"); dbgScript << "Plugin Directories: " << pluginDirectories; if (!Python::setPath(pluginDirectories)) { initStatus = INIT_CANNOT_SET_PYTHON_PATHS; return initStatus; } #if defined(IS_PY3K) if (0 != PyImport_AppendInittab(Python::PYKRITA_ENGINE, PyInit_pykrita)) { #else if (0 != PyImport_AppendInittab(Python::PYKRITA_ENGINE, initpykrita)) { #endif initStatus = INIT_CANNOT_LOAD_PYKRITA_MODULE; return initStatus; } Python::ensureInitialized(); Python py = Python(); // NOTE: This code is not needed on Python 3. // This might also fail if private sip module for PyQt5 is used, // as required by newer PyQt5. It's fine since any exceptions // in this script will be ignored. PyRun_SimpleString( "import sip\n" "sip.setapi('QDate', 2)\n" "sip.setapi('QTime', 2)\n" "sip.setapi('QDateTime', 2)\n" "sip.setapi('QUrl', 2)\n" "sip.setapi('QTextStream', 2)\n" "sip.setapi('QString', 2)\n" "sip.setapi('QVariant', 2)\n" ); // Initialize 'plugins' dict of module 'pykrita' PyObject* plugins = PyDict_New(); py.itemStringSet("plugins", plugins); pluginManagerInstance.reset(new PythonPluginManager()); #if defined(IS_PY3K) // Initialize our built-in module. auto pykritaModule = PyInit_pykrita(); if (!pykritaModule) { initStatus = INIT_CANNOT_LOAD_PYKRITA_MODULE; return initStatus; //return i18nc("@info:tooltip ", "No pykrita built-in module"); } #else initpykrita(); #endif initStatus = INIT_OK; return initStatus; } PythonPluginManager *pluginManager() { auto pluginManager = pluginManagerInstance.data(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(pluginManager, nullptr); return pluginManager; } void finalize() { dbgScript << "Going to destroy the Python engine"; if (pluginManagerInstance) { pluginManagerInstance->unloadAllModules(); PyKrita::Python::maybeFinalize(); PyKrita::Python::libraryUnload(); pluginManagerInstance.reset(); initStatus = INIT_UNINITIALIZED; } } namespace { #ifndef Q_OS_WIN QLibrary* s_pythonLibrary = 0; #endif PyThreadState* s_pythonThreadState = 0; bool isPythonPathSet = false; } // anonymous namespace const char* Python::PYKRITA_ENGINE = "pykrita"; Python::Python() { #if THREADED m_state = PyGILState_Ensure(); #endif } Python::~Python() { #if THREADED PyGILState_Release(m_state); #endif } bool Python::prependStringToList(PyObject* const list, const QString& value) { PyObject* const u = unicode(value); bool result = !PyList_Insert(list, 0, u); Py_DECREF(u); if (!result) traceback(QString("Failed to prepend %1").arg(value)); return result; } bool Python::functionCall(const char* const functionName, const char* const moduleName) { PyObject* const result = functionCall(functionName, moduleName, PyTuple_New(0)); if (result) Py_DECREF(result); return bool(result); } PyObject* Python::functionCall( const char* const functionName , const char* const moduleName , PyObject* const arguments ) { if (!arguments) { errScript << "Missing arguments for" << moduleName << functionName; return 0; } PyObject* const func = itemString(functionName, moduleName); if (!func) { errScript << "Failed to resolve" << moduleName << functionName; return 0; } if (!PyCallable_Check(func)) { traceback(QString("Not callable %1.%2").arg(moduleName).arg(functionName)); return 0; } PyObject* const result = PyObject_CallObject(func, arguments); Py_DECREF(arguments); if (!result) traceback(QString("No result from %1.%2").arg(moduleName).arg(functionName)); return result; } bool Python::itemStringDel(const char* const item, const char* const moduleName) { PyObject* const dict = moduleDict(moduleName); const bool result = dict && PyDict_DelItemString(dict, item); if (!result) traceback(QString("Could not delete item string %1.%2").arg(moduleName).arg(item)); return result; } PyObject* Python::itemString(const char* const item, const char* const moduleName) { if (PyObject* const value = itemString(item, moduleDict(moduleName))) return value; errScript << "Could not get item string" << moduleName << item; return 0; } PyObject* Python::itemString(const char* item, PyObject* dict) { if (dict) if (PyObject* const value = PyDict_GetItemString(dict, item)) return value; traceback(QString("Could not get item string %1").arg(item)); return 0; } bool Python::itemStringSet(const char* const item, PyObject* const value, const char* const moduleName) { PyObject* const dict = moduleDict(moduleName); const bool result = dict && !PyDict_SetItemString(dict, item, value); if (!result) traceback(QString("Could not set item string %1.%2").arg(moduleName).arg(item)); return result; } PyObject* Python::kritaHandler(const char* const moduleName, const char* const handler) { if (PyObject* const module = moduleImport(moduleName)) return functionCall(handler, "krita", Py_BuildValue("(O)", module)); return 0; } QString Python::lastTraceback() const { QString result; result.swap(m_traceback); return result; } bool Python::libraryLoad() { // no-op on Windows #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) if (!s_pythonLibrary) { QFileInfo fi(PYKRITA_PYTHON_LIBRARY); // get the filename of the configured Python library, without the .so suffix const QString libraryName = fi.completeBaseName(); // 1.0 is the SONAME of the shared Python library s_pythonLibrary = new QLibrary(libraryName, "1.0"); s_pythonLibrary->setLoadHints(QLibrary::ExportExternalSymbolsHint); if (!s_pythonLibrary->load()) { dbgScript << QString("Could not load %1 -- Reason: %2").arg(s_pythonLibrary->fileName()).arg(s_pythonLibrary->errorString()); delete s_pythonLibrary; s_pythonLibrary = 0; return false; } dbgScript << QString("Loaded %1").arg(s_pythonLibrary->fileName()); } #endif return true; } namespace { QString findKritaPythonLibsPath(const QString &libdir) { QDir rootDir(KoResourcePaths::getApplicationRoot()); QFileInfoList candidates = rootDir.entryInfoList(QStringList() << "lib*", QDir::Dirs | QDir::NoDotAndDotDot) + rootDir.entryInfoList(QStringList() << "Frameworks", QDir::Dirs | QDir::NoDotAndDotDot); Q_FOREACH (const QFileInfo &entry, candidates) { QDir libDir(entry.absoluteFilePath()); if (libDir.cd(libdir)) { return libDir.absolutePath(); } else { // Handle cases like Linux where libs are placed in a sub-dir // with the ABI name Q_FOREACH (const QFileInfo &subEntry, libDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) { QDir subDir(subEntry.absoluteFilePath()); if (subDir.cd(libdir)) { return subDir.absolutePath(); } } } } return QString(); } } // namespace bool Python::setPath(const QStringList& scriptPaths) { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!Py_IsInitialized(), false); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!isPythonPathSet, false); // qDebug() << ">>>>>>>>>>>" << qgetenv("APPDIR") // << KoResourcePaths::getApplicationRoot() // << (!qgetenv("APPDIR").isNull() && KoResourcePaths::getApplicationRoot().contains(qgetenv("APPDIR"))); bool runningInBundle = ((!qgetenv("APPDIR").isNull() && KoResourcePaths::getApplicationRoot().contains(qgetenv("APPDIR"))) || KoResourcePaths::getApplicationRoot().toLower().contains("krita.app")); dbgScript << "Python::setPath. Script paths:" << scriptPaths << runningInBundle; #ifdef Q_OS_WIN constexpr char pathSeparator = ';'; #else constexpr char pathSeparator = ':'; #endif QString originalPath; // Start with the script paths QStringList paths(scriptPaths); // Append the Krita libraries path QString pythonLibsPath = findKritaPythonLibsPath("krita-python-libs"); dbgScript << "pythonLibsPath (krita-python-libs)" << pythonLibsPath; if (pythonLibsPath.isEmpty()) { dbgScript << "Cannot find krita-python-libs"; return false; } dbgScript << "Found krita-python-libs at" << pythonLibsPath; paths.append(pythonLibsPath); #ifndef Q_OS_WIN // Append the sip libraries path pythonLibsPath = findKritaPythonLibsPath("sip"); dbgScript << "pythonLibsPath (sip)" << pythonLibsPath; if (!pythonLibsPath.isEmpty()) { dbgScript << "Found sip at" << pythonLibsPath; paths.append(pythonLibsPath); } #endif #ifdef Q_OS_WIN // Find embeddable Python at /python QDir pythonDir(KoResourcePaths::getApplicationRoot()); if (pythonDir.cd("python")) { dbgScript << "Found bundled Python at" << pythonDir.absolutePath(); // The default paths for Windows embeddable Python is ./python36.zip;./ // HACK: Assuming bundled Python is version 3.6.* // FIXME: Should we read python36._pth for the paths or use Py_GetPath? paths.append(pythonDir.absoluteFilePath("python36.zip")); paths.append(pythonDir.absolutePath()); } else { errScript << "Bundled Python not found, cannot set Python library paths"; return false; } #else // If using a system Python install, respect the current PYTHONPATH if (runningInBundle) { // We're running from an appimage, so we need our local python QString p = QFileInfo(PYKRITA_PYTHON_LIBRARY).fileName(); #ifdef Q_OS_MAC QString p2 = p.remove("lib").remove("m.dy"); #else QString p2 = p.remove("lib").remove("m.so"); #endif dbgScript << "\t" << p << p2; originalPath = findKritaPythonLibsPath(p); #ifdef Q_OS_MAC // Are we running with a system Python library instead? if (originalPath.isEmpty()) { // Keep the original Python search path. originalPath = QString::fromWCharArray(Py_GetPath()); QString d = QFileInfo(PYKRITA_PYTHON_LIBRARY).absolutePath(); paths.append(d + "/" + p2 + "/site-packages"); paths.append(d + "/" + p2 + "/site-packages/PyQt5"); } else { #endif paths.append(originalPath + "/lib-dynload"); paths.append(originalPath + "/site-packages"); paths.append(originalPath + "/site-packages/PyQt5"); #ifdef Q_OS_MAC } #endif } else { // Use the system path originalPath = QString::fromLocal8Bit(qgetenv("PYTHONPATH")); } #endif QString joinedPaths = paths.join(pathSeparator); if (!originalPath.isEmpty()) { joinedPaths = joinedPaths + pathSeparator + originalPath; } dbgScript << "Setting python paths:" << joinedPaths; #ifdef Q_OS_WIN QVector joinedPathsWChars(joinedPaths.size() + 1, 0); joinedPaths.toWCharArray(joinedPathsWChars.data()); Py_SetPath(joinedPathsWChars.data()); #else if (runningInBundle) { QVector joinedPathsWChars(joinedPaths.size() + 1, 0); joinedPaths.toWCharArray(joinedPathsWChars.data()); Py_SetPath(joinedPathsWChars.data()); } else { qputenv("PYTHONPATH", joinedPaths.toLocal8Bit()); } #endif isPythonPathSet = true; return true; } void Python::ensureInitialized() { if (Py_IsInitialized()) { warnScript << "Python interpreter is already initialized, not initializing again"; } else { dbgScript << "Initializing Python interpreter"; Py_InitializeEx(0); if (!Py_IsInitialized()) { errScript << "Could not initialize Python interpreter"; } #if THREADED PyEval_InitThreads(); s_pythonThreadState = PyGILState_GetThisThreadState(); PyEval_ReleaseThread(s_pythonThreadState); #endif } } void Python::maybeFinalize() { if (!Py_IsInitialized()) { warnScript << "Python interpreter not initialized, no need to finalize"; } else { #if THREADED PyEval_AcquireThread(s_pythonThreadState); #endif Py_Finalize(); } } void Python::libraryUnload() { // no-op on Windows #ifndef Q_OS_WIN if (s_pythonLibrary) { // Shut the interpreter down if it has been started. if (s_pythonLibrary->isLoaded()) { s_pythonLibrary->unload(); } delete s_pythonLibrary; s_pythonLibrary = 0; } #endif } PyObject* Python::moduleActions(const char* moduleName) { return kritaHandler(moduleName, "moduleGetActions"); } PyObject* Python::moduleConfigPages(const char* const moduleName) { return kritaHandler(moduleName, "moduleGetConfigPages"); } QString Python::moduleHelp(const char* moduleName) { QString r; PyObject* const result = kritaHandler(moduleName, "moduleGetHelp"); if (result) { r = unicode(result); Py_DECREF(result); } return r; } PyObject* Python::moduleDict(const char* const moduleName) { PyObject* const module = moduleImport(moduleName); if (module) if (PyObject* const dictionary = PyModule_GetDict(module)) return dictionary; traceback(QString("Could not get dict %1").arg(moduleName)); return 0; } PyObject* Python::moduleImport(const char* const moduleName) { PyObject* const module = PyImport_ImportModule(moduleName); if (module) return module; traceback(QString("Could not import %1").arg(moduleName)); return 0; } -// Inspired by http://www.gossamer-threads.com/lists/python/python/150924. +// Inspired by https://lists.gt.net/python/python/150924. void Python::traceback(const QString& description) { m_traceback.clear(); if (!PyErr_Occurred()) // Return an empty string on no error. // NOTE "Return a string?" really?? return; PyObject* exc_typ; PyObject* exc_val; PyObject* exc_tb; PyErr_Fetch(&exc_typ, &exc_val, &exc_tb); PyErr_NormalizeException(&exc_typ, &exc_val, &exc_tb); // Include the traceback. if (exc_tb) { m_traceback = "Traceback (most recent call last):\n"; PyObject* const arguments = PyTuple_New(1); PyTuple_SetItem(arguments, 0, exc_tb); PyObject* const result = functionCall("format_tb", "traceback", arguments); if (result) { for (int i = 0, j = PyList_Size(result); i < j; i++) { PyObject* const tt = PyList_GetItem(result, i); PyObject* const t = Py_BuildValue("(O)", tt); char* buffer; if (!PyArg_ParseTuple(t, "s", &buffer)) break; m_traceback += buffer; } Py_DECREF(result); } Py_DECREF(exc_tb); } // Include the exception type and value. if (exc_typ) { PyObject* const temp = PyObject_GetAttrString(exc_typ, "__name__"); if (temp) { m_traceback += unicode(temp); m_traceback += ": "; } Py_DECREF(exc_typ); } if (exc_val) { PyObject* const temp = PyObject_Str(exc_val); if (temp) { m_traceback += unicode(temp); m_traceback += "\n"; } Py_DECREF(exc_val); } m_traceback += description; QStringList l = m_traceback.split("\n"); Q_FOREACH(const QString &s, l) { errScript << s; } /// \todo How about to show it somewhere else than "console output"? } PyObject* Python::unicode(const QString& string) { #if PY_MAJOR_VERSION < 3 - /* Python 2.x. http://docs.python.org/2/c-api/unicode.html */ + /* Python 2.x. https://docs.python.org/2/c-api/unicode.html */ PyObject* s = PyString_FromString(PQ(string)); PyObject* u = PyUnicode_FromEncodedObject(s, "utf-8", "strict"); Py_DECREF(s); return u; #elif PY_MINOR_VERSION < 3 - /* Python 3.2 or less. http://docs.python.org/3.2/c-api/unicode.html#unicode-objects */ + /* Python 3.2 or less. https://docs.python.org/3.2/c-api/unicode.html#unicode-objects */ # ifdef Py_UNICODE_WIDE return PyUnicode_DecodeUTF16((const char*)string.constData(), string.length() * 2, 0, 0); # else return PyUnicode_FromUnicode(string.constData(), string.length()); # endif -#else /* Python 3.3 or greater. http://docs.python.org/3.3/c-api/unicode.html#unicode-objects */ +#else /* Python 3.3 or greater. https://docs.python.org/3.3/c-api/unicode.html#unicode-objects */ return PyUnicode_FromKindAndData(PyUnicode_2BYTE_KIND, string.constData(), string.length()); #endif } QString Python::unicode(PyObject* const string) { #if PY_MAJOR_VERSION < 3 - /* Python 2.x. http://docs.python.org/2/c-api/unicode.html */ + /* Python 2.x. https://docs.python.org/2/c-api/unicode.html */ if (PyString_Check(string)) return QString(PyString_AsString(string)); else if (PyUnicode_Check(string)) { const int unichars = PyUnicode_GetSize(string); # ifdef HAVE_USABLE_WCHAR_T return QString::fromWCharArray(PyUnicode_AsUnicode(string), unichars); # else # ifdef Py_UNICODE_WIDE return QString::fromUcs4((const unsigned int*)PyUnicode_AsUnicode(string), unichars); # else return QString::fromUtf16(PyUnicode_AsUnicode(string), unichars); # endif # endif } else return QString(); #elif PY_MINOR_VERSION < 3 - /* Python 3.2 or less. http://docs.python.org/3.2/c-api/unicode.html#unicode-objects */ + /* Python 3.2 or less. https://docs.python.org/3.2/c-api/unicode.html#unicode-objects */ if (!PyUnicode_Check(string)) return QString(); const int unichars = PyUnicode_GetSize(string); # ifdef HAVE_USABLE_WCHAR_T return QString::fromWCharArray(PyUnicode_AsUnicode(string), unichars); # else # ifdef Py_UNICODE_WIDE return QString::fromUcs4(PyUnicode_AsUnicode(string), unichars); # else return QString::fromUtf16(PyUnicode_AsUnicode(string), unichars); # endif # endif -#else /* Python 3.3 or greater. http://docs.python.org/3.3/c-api/unicode.html#unicode-objects */ +#else /* Python 3.3 or greater. https://docs.python.org/3.3/c-api/unicode.html#unicode-objects */ if (!PyUnicode_Check(string)) return QString(); const int unichars = PyUnicode_GetLength(string); if (0 != PyUnicode_READY(string)) return QString(); switch (PyUnicode_KIND(string)) { case PyUnicode_1BYTE_KIND: return QString::fromLatin1((const char*)PyUnicode_1BYTE_DATA(string), unichars); case PyUnicode_2BYTE_KIND: return QString::fromUtf16(PyUnicode_2BYTE_DATA(string), unichars); case PyUnicode_4BYTE_KIND: return QString::fromUcs4(PyUnicode_4BYTE_DATA(string), unichars); default: break; } return QString(); #endif } bool Python::isUnicode(PyObject* const string) { #if PY_MAJOR_VERSION < 3 return PyString_Check(string) || PyUnicode_Check(string); #else return PyUnicode_Check(string); #endif } bool Python::prependPythonPaths(const QString& path) { PyObject* sys_path = itemString("path", "sys"); return bool(sys_path) && prependPythonPaths(path, sys_path); } bool Python::prependPythonPaths(const QStringList& paths) { PyObject* sys_path = itemString("path", "sys"); if (!sys_path) return false; /// \todo Heh, boosts' range adaptors would be good here! QStringList reversed_paths; std::reverse_copy( paths.begin() , paths.end() , std::back_inserter(reversed_paths) ); Q_FOREACH(const QString & path, reversed_paths) if (!prependPythonPaths(path, sys_path)) return false; return true; } bool Python::prependPythonPaths(const QString& path, PyObject* sys_path) { Q_ASSERT("Dir entry expected to be valid" && sys_path); return bool(prependStringToList(sys_path, path)); } } // namespace PyKrita // krita: indent-width 4; diff --git a/plugins/extensions/pykrita/sip/krita/ManagedColor.sip b/plugins/extensions/pykrita/sip/krita/ManagedColor.sip index a8371be9ef..104c589e9c 100644 --- a/plugins/extensions/pykrita/sip/krita/ManagedColor.sip +++ b/plugins/extensions/pykrita/sip/krita/ManagedColor.sip @@ -1,24 +1,25 @@ class ManagedColor : QObject { %TypeHeaderCode #include "ManagedColor.h" %End ManagedColor(const ManagedColor & __0); public: ManagedColor(const QString &colorModel, const QString &colorDepth, const QString &colorProfile, QObject *parent = 0); bool operator==(const ManagedColor &other) const; QColor colorForCanvas(Canvas *canvas) const; + static ManagedColor *fromQColor(const QColor &qcolor, Canvas *canvas = 0); QString colorDepth() const; QString colorModel() const; QString colorProfile() const; bool setColorProfile(const QString &colorProfile); bool setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile); QVector components() const; QVector componentsOrdered() const; void setComponents(const QVector &values); QString toXML() const; void fromXML(const QString &xml); QString toQString(); private: }; diff --git a/plugins/extensions/qmic/WdgQMicSettings.ui b/plugins/extensions/qmic/WdgQMicSettings.ui index dc1678f3a8..1d70a449b0 100644 --- a/plugins/extensions/qmic/WdgQMicSettings.ui +++ b/plugins/extensions/qmic/WdgQMicSettings.ui @@ -1,79 +1,79 @@ WdgQMicSettings 0 0 400 300 - <html><head/><body><p>Select the location of the G'Mic-Qt plugin. You can download the plugin from the <a href="http://gmic.eu/"><span style=" text-decoration: underline; color:#2980b9;">G'Mic website</span></a>. Make sure you download the special version for Krita, not the standalone or the GIMP version.</p></body></html> + <html><head/><body><p>Select the location of the G'Mic-Qt plugin. You can download the plugin from the <a href="https://gmic.eu/"><span style=" text-decoration: underline; color:#2980b9;">G'Mic website</span></a>. Make sure you download the special version for Krita, not the standalone or the GIMP version.</p></body></html> Qt::RichText true true Qt::TextBrowserInteraction Plugin: 1 0 Qt::Vertical 20 40 KisFileNameRequester QWidget
    kis_file_name_requester.h
    1
    diff --git a/plugins/filters/blur/kis_blur_filter.cpp b/plugins/filters/blur/kis_blur_filter.cpp index 183165b751..6138bbab4f 100644 --- a/plugins/filters/blur/kis_blur_filter.cpp +++ b/plugins/filters/blur/kis_blur_filter.cpp @@ -1,139 +1,137 @@ /* * This file is part of Krita * * Copyright (c) 2006 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include //MSVC requires that Vc come first #include "kis_blur_filter.h" #include #include #include #include "kis_wdg_blur.h" #include "ui_wdgblur.h" #include #include #include #include #include #include "kis_mask_generator.h" #include "kis_lod_transform.h" KisBlurFilter::KisBlurFilter() : KisFilter(id(), FiltersCategoryBlurId, i18n("&Blur...")) { setSupportsPainting(true); setSupportsAdjustmentLayers(true); setSupportsLevelOfDetail(true); setColorSpaceIndependence(FULLY_INDEPENDENT); } KisConfigWidget * KisBlurFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool) const { return new KisWdgBlur(parent); } KisFilterConfigurationSP KisBlurFilter::defaultConfiguration() const { KisFilterConfigurationSP config = factoryConfiguration(); config->setProperty("halfWidth", 5); config->setProperty("halfHeight", 5); config->setProperty("rotate", 0); config->setProperty("strength", 0); config->setProperty("shape", 0); return config; } void KisBlurFilter::processImpl(KisPaintDeviceSP device, const QRect& rect, const KisFilterConfigurationSP _config, KoUpdater* progressUpdater ) const { QPoint srcTopLeft = rect.topLeft(); Q_ASSERT(device != 0); KisFilterConfigurationSP config = _config ? _config : new KisFilterConfiguration(id().id(), 1); KisLodTransformScalar t(device); QVariant value; const uint halfWidth = t.scale((config->getProperty("halfWidth", value)) ? value.toUInt() : 5); const uint halfHeight = t.scale((config->getProperty("halfHeight", value)) ? value.toUInt() : 5); int shape = (config->getProperty("shape", value)) ? value.toInt() : 0; uint width = 2 * halfWidth + 1; uint height = 2 * halfHeight + 1; - float aspectRatio = (float) width / height; + qreal aspectRatio = (qreal) height / width; int rotate = (config->getProperty("rotate", value)) ? value.toInt() : 0; - int strength = 100 - (config->getProperty("strength", value) ? value.toUInt() : 0); - - int hFade = (halfWidth * strength) / 100; - int vFade = (halfHeight * strength) / 100; + qreal strength = (config->getProperty("strength", value) ? value.toUInt() : 0) / (qreal) 100; + qreal hFade, vFade = strength; KisMaskGenerator* kas; dbgKrita << width << "" << height << "" << hFade << "" << vFade; switch (shape) { case 1: kas = new KisRectangleMaskGenerator(width, aspectRatio, hFade, vFade, 2, true); break; case 0: default: kas = new KisCircleMaskGenerator(width, aspectRatio, hFade, vFade, 2, true); break; } QBitArray channelFlags; if (config) { channelFlags = config->channelFlags(); } if (channelFlags.isEmpty() || !config) { channelFlags = QBitArray(device->colorSpace()->channelCount(), true); } KisConvolutionKernelSP kernel = KisConvolutionKernel::fromMaskGenerator(kas, rotate * M_PI / 180.0); delete kas; KisConvolutionPainter painter(device); painter.setChannelFlags(channelFlags); painter.setProgress(progressUpdater); painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); } QRect KisBlurFilter::neededRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; const int halfWidth = t.scale(_config->getProperty("halfWidth", value) ? value.toUInt() : 5); const int halfHeight = t.scale(_config->getProperty("halfHeight", value) ? value.toUInt() : 5); return rect.adjusted(-halfWidth * 2, -halfHeight * 2, halfWidth * 2, halfHeight * 2); } QRect KisBlurFilter::changedRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; const int halfWidth = t.scale(_config->getProperty("halfWidth", value) ? value.toUInt() : 5); const int halfHeight = t.scale(_config->getProperty("halfHeight", value) ? value.toUInt() : 5); return rect.adjusted(-halfWidth, -halfHeight, halfWidth, halfHeight); } diff --git a/plugins/filters/blur/wdgblur.ui b/plugins/filters/blur/wdgblur.ui index 8909d81fa0..d45478839b 100644 --- a/plugins/filters/blur/wdgblur.ui +++ b/plugins/filters/blur/wdgblur.ui @@ -1,221 +1,227 @@ WdgBlur 0 0 280 190 0 0 0 0 Horizontal Radius: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false px 1 + + 1000 + Qt::Horizontal QSizePolicy::Expanding 0 20 Vertical Radius: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false px 1 + + 1000 + Strength: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false 100 100 Angle: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false ° 360 Shape: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false Circle Rectangle Qt::Horizontal QSizePolicy::Expanding 0 20 Qt::Vertical QSizePolicy::Expanding 20 20 KComboBox QComboBox
    kcombobox.h
    KoAspectButton QWidget
    KoAspectButton.h
    1
    KisIntParseSpinBox QSpinBox
    kis_int_parse_spin_box.h
    diff --git a/plugins/filters/tests/CMakeLists.txt b/plugins/filters/tests/CMakeLists.txt index 80eaef3e4a..2275323b9f 100644 --- a/plugins/filters/tests/CMakeLists.txt +++ b/plugins/filters/tests/CMakeLists.txt @@ -1,12 +1,17 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ) macro_add_unittest_definitions() +##### Tests that currently fail and should be fixed ##### -########### next target ############### +include(KritaAddBrokenUnitTest) -ecm_add_tests( - kis_all_filter_test.cpp - kis_crash_filter_test.cpp +krita_add_broken_unit_test( kis_all_filter_test.cpp + TEST_NAME kis_all_filter_test.cpp + NAME_PREFIX "krita-filters-" + LINK_LIBRARIES kritaimage Qt5::Test) + +krita_add_broken_unit_test( kis_crash_filter_test.cpp + TEST_NAME kis_crash_filter_test.cpp NAME_PREFIX "krita-filters-" LINK_LIBRARIES kritaimage Qt5::Test) diff --git a/plugins/flake/artistictextshape/ArtisticTextShapeConfigWidget.ui b/plugins/flake/artistictextshape/ArtisticTextShapeConfigWidget.ui index b081e19102..53c0b931bc 100644 --- a/plugins/flake/artistictextshape/ArtisticTextShapeConfigWidget.ui +++ b/plugins/flake/artistictextshape/ArtisticTextShapeConfigWidget.ui @@ -1,161 +1,156 @@ ArtisticTextShapeConfigWidget 0 0 259 73 0 0 0 0 0 0 - + 0 0 0 0 Qt::Vertical Qt::Vertical Qt::Horizontal 40 20 Qt::Vertical 20 40 KisIntParseSpinBox QSpinBox
    kis_int_parse_spin_box.h
    - - KoFontComboBox - QComboBox -
    KoFontComboBox.h
    -
    diff --git a/plugins/flake/textshape/FontFamilyAction.cpp b/plugins/flake/textshape/FontFamilyAction.cpp index 09f424e073..58f267ceba 100644 --- a/plugins/flake/textshape/FontFamilyAction.cpp +++ b/plugins/flake/textshape/FontFamilyAction.cpp @@ -1,194 +1,194 @@ /* This file is part of the KDE libraries Copyright (C) 1999 Reginald Stadlbauer (C) 1999 Simon Hausmann (C) 2000 Nicolas Hadacek (C) 2000 Kurt Granroth (C) 2000 Michael Koch (C) 2001 Holger Freyther (C) 2002 Ellis Whitehead (C) 2002 Joseph Wenninger (C) 2003 Andras Mantia (C) 2005-2006 Hamish Rodda (C) 2007 Clarence Dang (C) 2014 Dan Leinir Turthra Jensen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // This is a minorly modified version of the KFontAction class. It exists // entirely because there's a hang bug on windows at the moment. #include "FontFamilyAction.h" #include #include #include #include #include -#include +#include class KoFontFamilyAction::KoFontFamilyActionPrivate { public: KoFontFamilyActionPrivate(KoFontFamilyAction *parent) : q(parent) , settingFont(0) { } void _ko_slotFontChanged(const QFont &font) { - qDebug() << "KoFontComboBox - slotFontChanged(" + qDebug() << "QFontComboBox - slotFontChanged(" << font.family() << ") settingFont=" << settingFont; if (settingFont) { return; } q->setFont(font.family()); q->triggered(font.family()); qDebug() << "\tslotFontChanged done"; } KoFontFamilyAction *q; int settingFont; }; KoFontFamilyAction::KoFontFamilyAction(uint fontListCriteria, QObject *parent) : KSelectAction(parent) , d(new KoFontFamilyActionPrivate(this)) { QStringList list; KFontChooser::getFontList(list, fontListCriteria); KSelectAction::setItems(list); setEditable(true); } KoFontFamilyAction::KoFontFamilyAction(QObject *parent) : KSelectAction(parent) , d(new KoFontFamilyActionPrivate(this)) { QStringList list; KFontChooser::getFontList(list, 0); KSelectAction::setItems(list); setEditable(true); } KoFontFamilyAction::KoFontFamilyAction(const QString &text, QObject *parent) : KSelectAction(text, parent) , d(new KoFontFamilyActionPrivate(this)) { QStringList list; KFontChooser::getFontList(list, 0); KSelectAction::setItems(list); setEditable(true); } KoFontFamilyAction::KoFontFamilyAction(const QIcon &icon, const QString &text, QObject *parent) : KSelectAction(icon, text, parent) , d(new KoFontFamilyActionPrivate(this)) { QStringList list; KFontChooser::getFontList(list, 0); KSelectAction::setItems(list); setEditable(true); } KoFontFamilyAction::~KoFontFamilyAction() { delete d; } QString KoFontFamilyAction::font() const { return currentText(); } QWidget *KoFontFamilyAction::createWidget(QWidget *parent) { qDebug() << "KoFontFamilyAction::createWidget()"; // silence unclear warning from original kfontaction.acpp // #ifdef __GNUC__ // #warning FIXME: items need to be converted // #endif // This is the visual element on the screen. This method overrides // the KSelectAction one, preventing KSelectAction from creating its // regular KComboBox. - KoFontComboBox *cb = new KoFontComboBox(parent); + QFontComboBox *cb = new QFontComboBox(parent); //cb->setFontList(items()); qDebug() << "\tset=" << font(); // Do this before connecting the signal so that nothing will fire. cb->setCurrentFont(QFont(font().toLower())); qDebug() << "\tspit back=" << cb->currentFont().family(); connect(cb, SIGNAL(currentFontChanged(QFont)), SLOT(_ko_slotFontChanged(QFont))); cb->setMinimumWidth(cb->sizeHint().width()); return cb; } /* * Maintenance note: Keep in sync with KFontComboBox::setCurrentFont() */ void KoFontFamilyAction::setFont(const QString &family) { qDebug() << "KoFontFamilyAction::setFont(" << family << ")"; // Suppress triggered(QString) signal and prevent recursive call to ourself. d->settingFont++; Q_FOREACH (QWidget *w, createdWidgets()) { - KoFontComboBox *cb = qobject_cast(w); + QFontComboBox *cb = qobject_cast(w); qDebug() << "\tw=" << w << "cb=" << cb; if (!cb) { continue; } cb->setCurrentFont(QFont(family.toLower())); qDebug() << "\t\tw spit back=" << cb->currentFont().family(); } d->settingFont--; qDebug() << "\tcalling setCurrentAction()"; QString lowerName = family.toLower(); if (setCurrentAction(lowerName, Qt::CaseInsensitive)) { return; } int i = lowerName.indexOf(" ["); if (i > -1) { lowerName = lowerName.left(i); i = 0; if (setCurrentAction(lowerName, Qt::CaseInsensitive)) { return; } } lowerName += " ["; if (setCurrentAction(lowerName, Qt::CaseInsensitive)) { return; } // TODO: Inconsistent state if KFontComboBox::setCurrentFont() succeeded // but setCurrentAction() did not and vice-versa. qDebug() << "Font not found " << family.toLower(); } // have to include this because of Q_PRIVATE_SLOT #include "moc_FontFamilyAction.cpp" diff --git a/plugins/flake/textshape/kotext/KoTextBlockData.cpp b/plugins/flake/textshape/kotext/KoTextBlockData.cpp index 608322aac3..1fbdbf8a81 100644 --- a/plugins/flake/textshape/kotext/KoTextBlockData.cpp +++ b/plugins/flake/textshape/kotext/KoTextBlockData.cpp @@ -1,302 +1,302 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2010 C. Boemann * 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. */ #include "KoTextBlockData.h" #include "KoTextBlockBorderData.h" #include "KoTextBlockPaintStrategyBase.h" class Q_DECL_HIDDEN KoTextBlockData::Private : public QTextBlockUserData { public: Private() : counterWidth(-1.0) , counterSpacing(0) , counterIsImage(false) , counterIndex(1) , border(0) , paintStrategy(0) { layoutedMarkupRanges[KoTextBlockData::Misspell] = false; layoutedMarkupRanges[KoTextBlockData::Grammar] = false; } ~Private() override { if (border && !border->deref()) delete border; delete paintStrategy; } qreal counterWidth; qreal counterSpacing; QString counterPrefix; QString counterPlainText; QString counterSuffix; QString partialCounterText; bool counterIsImage; int counterIndex; QPointF counterPos; QTextCharFormat labelFormat; KoTextBlockBorderData *border; KoTextBlockPaintStrategyBase *paintStrategy; QMap > markupRangesMap; QMap layoutedMarkupRanges; }; KoTextBlockData::KoTextBlockData(QTextBlock &block) : d(block.userData() ? dynamic_cast(block.userData()) : new Private()) { block.setUserData(d); } KoTextBlockData::KoTextBlockData(QTextBlockUserData *userData) : d(dynamic_cast(userData)) { Q_ASSERT(d); } KoTextBlockData::~KoTextBlockData() { // explicitly do not delete the d-pointer here } void KoTextBlockData::appendMarkup(MarkupType type, int firstChar, int lastChar) { Q_ASSERT(d->markupRangesMap[type].isEmpty() || d->markupRangesMap[type].last().lastChar < firstChar); MarkupRange range; range.firstChar = firstChar; range.lastChar = lastChar; d->layoutedMarkupRanges[type] = false; d->markupRangesMap[type].append(range); } void KoTextBlockData::clearMarkups(MarkupType type) { d->markupRangesMap[type].clear(); d->layoutedMarkupRanges[type] = false; } KoTextBlockData::MarkupRange KoTextBlockData::findMarkup(MarkupType type, int positionWithin) const { foreach (const MarkupRange &range, d->markupRangesMap[type]) { if (positionWithin <= range.lastChar) { // possible hit if (positionWithin >= range.firstChar) { return range; } else { return MarkupRange(); // we have passed it without finding } } } return MarkupRange(); // either no ranges or not in last either } void KoTextBlockData::rebaseMarkups(MarkupType type, int fromPosition, int delta) { QList::Iterator markIt = markupsBegin(type); QList::Iterator markEnd = markupsEnd(type); while (markIt != markEnd) { if (fromPosition <= markIt->lastChar) { // we need to modify the end of this markIt->lastChar += delta; } if (fromPosition < markIt->firstChar) { // we need to modify the end of this markIt->firstChar += delta; } ++markIt; } } void KoTextBlockData::setMarkupsLayoutValidity(MarkupType type, bool valid) { d->layoutedMarkupRanges[type] = valid; } bool KoTextBlockData::isMarkupsLayoutValid(MarkupType type) const { return d->layoutedMarkupRanges[type]; } QList::Iterator KoTextBlockData::markupsBegin(MarkupType type) { return d->markupRangesMap[type].begin(); } QList::Iterator KoTextBlockData::markupsEnd(MarkupType type) { return d->markupRangesMap[type].end(); } bool KoTextBlockData::hasCounterData() const { return d->counterWidth >= 0 && (!d->counterPlainText.isNull() || d->counterIsImage); } qreal KoTextBlockData::counterWidth() const { return qMax(qreal(0), d->counterWidth); } void KoTextBlockData::setBorder(KoTextBlockBorderData *border) { if (d->border && !d->border->deref()) delete d->border; d->border = border; if (d->border) d->border->ref(); } void KoTextBlockData::setCounterWidth(qreal width) { d->counterWidth = width; } qreal KoTextBlockData::counterSpacing() const { return d->counterSpacing; } void KoTextBlockData::setCounterSpacing(qreal spacing) { d->counterSpacing = spacing; } QString KoTextBlockData::counterText() const { return d->counterPrefix + d->counterPlainText + d->counterSuffix; } void KoTextBlockData::clearCounter() { d->partialCounterText.clear(); d->counterPlainText.clear(); d->counterPrefix.clear(); d->counterSuffix.clear(); d->counterSpacing = 0.0; d->counterWidth = 0.0; d->counterIsImage = false; } void KoTextBlockData::setPartialCounterText(const QString &text) { d->partialCounterText = text; } QString KoTextBlockData::partialCounterText() const { return d->partialCounterText; } void KoTextBlockData::setCounterPlainText(const QString &text) { d->counterPlainText = text; } QString KoTextBlockData::counterPlainText() const { return d->counterPlainText; } void KoTextBlockData::setCounterPrefix(const QString &text) { d->counterPrefix = text; } QString KoTextBlockData::counterPrefix() const { return d->counterPrefix; } void KoTextBlockData::setCounterSuffix(const QString &text) { d->counterSuffix = text; } QString KoTextBlockData::counterSuffix() const { return d->counterSuffix; } void KoTextBlockData::setCounterIsImage(bool isImage) { d->counterIsImage = isImage; } bool KoTextBlockData::counterIsImage() const { return d->counterIsImage; } void KoTextBlockData::setCounterIndex(int index) { d->counterIndex = index; } int KoTextBlockData::counterIndex() const { return d->counterIndex; } void KoTextBlockData::setCounterPosition(const QPointF &position) { d->counterPos = position; } QPointF KoTextBlockData::counterPosition() const { return d->counterPos; } void KoTextBlockData::setLabelFormat(const QTextCharFormat &format) { d->labelFormat = format; } QTextCharFormat KoTextBlockData::labelFormat() const { return d->labelFormat; } KoTextBlockBorderData *KoTextBlockData::border() const { return d->border; } void KoTextBlockData::setPaintStrategy(KoTextBlockPaintStrategyBase *paintStrategy) { delete d->paintStrategy; d->paintStrategy = paintStrategy; } KoTextBlockPaintStrategyBase *KoTextBlockData::paintStrategy() const { return d->paintStrategy; } bool KoTextBlockData::saveXmlID() const { - // as suggested by boemann, http://lists.kde.org/?l=calligra-devel&m=132396354701553&w=2 + // as suggested by boemann, https://marc.info/?l=calligra-devel&m=132396354701553&w=2 return d->paintStrategy != 0; } diff --git a/plugins/flake/textshape/kotext/KoVariable.cpp b/plugins/flake/textshape/kotext/KoVariable.cpp index b878eeefb5..c8889797ac 100644 --- a/plugins/flake/textshape/kotext/KoVariable.cpp +++ b/plugins/flake/textshape/kotext/KoVariable.cpp @@ -1,168 +1,168 @@ /* This file is part of the KDE project * Copyright (C) 2006-2009 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoVariable.h" #include "KoInlineObject_p.h" #include #include #include #include #include #include "TextDebug.h" class KoVariablePrivate : public KoInlineObjectPrivate { public: KoVariablePrivate() : modified(true), document(0), lastPositionInDocument(-1) { } QDebug printDebug(QDebug dbg) const override { dbg.nospace() << "KoVariable value=" << value; return dbg.space(); } QString value; bool modified; const QTextDocument *document; int lastPositionInDocument; }; KoVariable::KoVariable(bool propertyChangeListener) : KoInlineObject(*(new KoVariablePrivate()), propertyChangeListener) { } KoVariable::~KoVariable() { } void KoVariable::setValue(const QString &value) { Q_D(KoVariable); if (d->value == value) return; d->value = value; d->modified = true; if (d->document) { const_cast(d->document)->markContentsDirty(d->lastPositionInDocument, 0); } } void KoVariable::updatePosition(const QTextDocument *document, int posInDocument, const QTextCharFormat & format) { Q_D(KoVariable); if (d->document) { disconnect(d->document, SIGNAL(destroyed()), this, SLOT(documentDestroyed())); } d->document = document; connect(d->document, SIGNAL(destroyed()), this, SLOT(documentDestroyed())); d->lastPositionInDocument = posInDocument; Q_UNUSED(format); // Variables are always 'in place' so the position is 100% defined by the text layout. variableMoved(d->document, posInDocument); } void KoVariable::resize(const QTextDocument *document, QTextInlineObject &object, int posInDocument, const QTextCharFormat &format, QPaintDevice *pd) { Q_D(KoVariable); Q_UNUSED(document); Q_UNUSED(posInDocument); if (d->modified == false) return; if (object.isValid() == false) return; d->modified = true; Q_ASSERT(format.isCharFormat()); QFontMetricsF fm(format.font(), pd); qreal width = qMax(qreal(0.0), fm.width(d->value)); qreal ascent = fm.ascent(); qreal descent = fm.descent(); if (object.width() != width) { object.setWidth(width); } if (object.ascent() != ascent) { object.setAscent(ascent); } if (object.descent() != descent) { object.setDescent(descent); } } void KoVariable::paint(QPainter &painter, QPaintDevice *pd, const QTextDocument *document, const QRectF &rect, const QTextInlineObject &object, int posInDocument, const QTextCharFormat &format) { Q_D(KoVariable); Q_UNUSED(document); Q_UNUSED(posInDocument); // TODO set all the font properties from the format (color etc) QFont font(format.font(), pd); QTextLayout layout(d->value, font, pd); layout.setCacheEnabled(true); QVector layouts; QTextLayout::FormatRange range; range.start = 0; range.length = d->value.length(); range.format = format; layouts.append(range); layout.setFormats(layouts); QTextOption option(Qt::AlignLeft | Qt::AlignAbsolute); if (object.isValid()) { option.setTextDirection(object.textDirection()); } layout.setTextOption(option); layout.beginLayout(); layout.createLine(); layout.endLayout(); layout.draw(&painter, rect.topLeft()); } void KoVariable::variableMoved(const QTextDocument *document, int posInDocument) { Q_UNUSED(document); Q_UNUSED(posInDocument); } QString KoVariable::value() const { Q_D(const KoVariable); return d->value; } int KoVariable::positionInDocument() const { Q_D(const KoVariable); return d->lastPositionInDocument; } void KoVariable::documentDestroyed() { // deleteLater(); does not work when closing a document as the inline object manager is deleted before the control is given back to the event loop // therefore commit suicide. - // See http://www.parashift.com/c++-faq-lite/delete-this.html + // See https://isocpp.org/wiki/faq/freestore-mgmt#delete-this delete(this); } diff --git a/plugins/flake/textshape/kotext/styles/KoCharacterStyle.cpp b/plugins/flake/textshape/kotext/styles/KoCharacterStyle.cpp index 0c82bf4715..bac90fbecd 100644 --- a/plugins/flake/textshape/kotext/styles/KoCharacterStyle.cpp +++ b/plugins/flake/textshape/kotext/styles/KoCharacterStyle.cpp @@ -1,2268 +1,2268 @@ /* This file is part of the KDE project * Copyright (C) 2006-2009 Thomas Zander * Copyright (C) 2007 Sebastian Sauer * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2011 Stuart Dickson * * 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 "KoCharacterStyle.h" #include "Styles_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoTextDocument.h" #ifdef SHOULD_BUILD_FONT_CONVERSION #include #include #include #include #include FT_FREETYPE_H #include FT_GLYPH_H #include FT_TYPES_H #include FT_OUTLINE_H #include FT_RENDER_H #include FT_TRUETYPE_TABLES_H #include FT_SFNT_NAMES_H #endif #include "TextDebug.h" #include "KoTextDebug.h" #ifdef SHOULD_BUILD_FONT_CONVERSION QMap textScaleMap; #endif //SHOULD_BUILD_FONT_CONVERSION class Q_DECL_HIDDEN KoCharacterStyle::Private { public: Private(); ~Private() { } void setProperty(int key, const QVariant &value) { stylesPrivate.add(key, value); } qreal propertyDouble(int key) const { QVariant variant = stylesPrivate.value(key); if (variant.isNull()) { if (parentStyle) return parentStyle->d->propertyDouble(key); else if (defaultStyle) return defaultStyle->d->propertyDouble(key); return 0.0; } return variant.toDouble(); } int propertyInt(int key) const { QVariant variant = stylesPrivate.value(key); if (variant.isNull()) { if (parentStyle) return parentStyle->d->propertyInt(key); else if (defaultStyle) return defaultStyle->d->propertyInt(key); return 0; } return variant.toInt(); } QString propertyString(int key) const { QVariant variant = stylesPrivate.value(key); if (variant.isNull()) { if (parentStyle) return parentStyle->d->propertyString(key); else if (defaultStyle) return defaultStyle->d->propertyString(key); return QString(); } return qvariant_cast(variant); } bool propertyBoolean(int key) const { QVariant variant = stylesPrivate.value(key); if (variant.isNull()) { if (parentStyle) return parentStyle->d->propertyBoolean(key); else if (defaultStyle) return defaultStyle->d->propertyBoolean(key); return false; } return variant.toBool(); } QColor propertyColor(int key) const { QVariant variant = stylesPrivate.value(key); if (variant.isNull()) { if (parentStyle) return parentStyle->d->propertyColor(key); else if (defaultStyle) return defaultStyle->d->propertyColor(key); return QColor(); } return variant.value(); } // problem with fonts in linux and windows is that true type fonts have more than one metric // they have normal metric placed in font header table // microsoft metric placed in os2 table // apple metric placed in os2 table // ms-word is probably using CreateFontIndirect and GetOutlineTextMetric function to calculate line height // and this functions are using windows gdi environment which is using microsoft font metric placed in os2 table // qt on linux is using normal font metric // this two metrics are different and change from font to font // this font stretch is needed if we want to have exact line height as in ms-word and oo // // font_size * font_stretch = windows_font_height qreal calculateFontYStretch(const QString &fontFamily); StylePrivate hardCodedDefaultStyle; QString name; StylePrivate stylesPrivate; KoCharacterStyle *parentStyle; KoCharacterStyle *defaultStyle; bool m_inUse; }; KoCharacterStyle::Private::Private() : parentStyle(0), defaultStyle(0), m_inUse(false) { //set the minimal default properties hardCodedDefaultStyle.add(QTextFormat::FontFamily, QString("Sans Serif")); hardCodedDefaultStyle.add(QTextFormat::FontPointSize, 12.0); hardCodedDefaultStyle.add(QTextFormat::ForegroundBrush, QBrush(Qt::black)); hardCodedDefaultStyle.add(KoCharacterStyle::FontYStretch, 1); hardCodedDefaultStyle.add(QTextFormat::FontHintingPreference, QFont::PreferNoHinting); } void KoCharacterStyle::ensureMinimalProperties(QTextCharFormat &format) const { if (d->defaultStyle) { QMap props = d->defaultStyle->d->stylesPrivate.properties(); QMap::const_iterator it = props.constBegin(); while (it != props.constEnd()) { // in case there is already a foreground color don't apply the use window font color as then the foreground color // should be used. if (it.key() == KoCharacterStyle::UseWindowFontColor && format.hasProperty(QTextFormat::ForegroundBrush)) { ++it; continue; } // in case there is already a use window font color don't apply the foreground brush as this overwrite the foreground color if (it.key() == QTextFormat::ForegroundBrush && format.hasProperty(KoCharacterStyle::UseWindowFontColor)) { ++it; continue; } if (!it.value().isNull() && !format.hasProperty(it.key())) { format.setProperty(it.key(), it.value()); } ++it; } } QMap props = d->hardCodedDefaultStyle.properties(); QMap::const_iterator it = props.constBegin(); while (it != props.constEnd()) { if (!it.value().isNull() && !format.hasProperty(it.key())) { if (it.key() == QTextFormat::ForegroundBrush && format.hasProperty(KoCharacterStyle::UseWindowFontColor)) { ++it; continue; } format.setProperty(it.key(), it.value()); } ++it; } } qreal KoCharacterStyle::Private::calculateFontYStretch(const QString &fontFamily) { qreal stretch = 1; #ifdef SHOULD_BUILD_FONT_CONVERSION if (textScaleMap.contains(fontFamily)) { return textScaleMap.value(fontFamily); } FcResult result = FcResultMatch; FT_Library library; FT_Face face; int id = 0; int error = 0; QByteArray fontName = fontFamily.toLatin1(); - //TODO http://freedesktop.org/software/fontconfig/fontconfig-devel/x19.html + //TODO https://freedesktop.org/software/fontconfig/fontconfig-devel/x19.html // we should specify slant and weight too FcPattern *font = FcPatternBuild (0, FC_FAMILY, FcTypeString,fontName.data(), FC_SIZE, FcTypeDouble, (qreal)11, 0); if (font == 0) { return 1; } // find font FcPattern *matched = 0; matched = FcFontMatch (0, font, &result); if (matched == 0) { FcPatternDestroy (font); return 1; } // get font family name char * str = 0; result = FcPatternGetString (matched, FC_FAMILY, 0,(FcChar8**) &str); if (result != FcResultMatch || str == 0) { FcPatternDestroy (font); FcPatternDestroy (matched); return 1; } // check if right font was found QByteArray foundFontFamily = QByteArray::fromRawData(str, strlen(str)); if (foundFontFamily != fontName) { FcPatternDestroy (font); FcPatternDestroy (matched); return 1; } // get path to font str = 0; result = FcPatternGetString (matched, FC_FILE, 0,(FcChar8**) &str); if (result != FcResultMatch) { FcPatternDestroy (font); FcPatternDestroy (matched); return 1; } // get index of font inside the font file result = FcPatternGetInteger (matched, FC_INDEX, 0, &id); if (result != FcResultMatch) { FcPatternDestroy (font); FcPatternDestroy (matched); return 1; } // initialize freetype error = FT_Init_FreeType( &library ); if (error) { FcPatternDestroy (font); FcPatternDestroy (matched); return 1; } // get font metric error = FT_New_Face (library,(char *) str, id, &face); if (error) { FT_Done_FreeType(library); FcPatternDestroy (font); FcPatternDestroy (matched); return 1; } // get font metric os2 table TT_OS2 *os2; os2 = (TT_OS2 *) FT_Get_Sfnt_Table (face, ft_sfnt_os2); if(os2 == 0) { FT_Done_Face(face); FT_Done_FreeType(library); FcPatternDestroy (font); FcPatternDestroy (matched); return 1; } // get font metric header table TT_Header *header; header = (TT_Header *) FT_Get_Sfnt_Table (face, ft_sfnt_head); if(header == 0) { FT_Done_Face(face); FT_Done_FreeType(library); FcPatternDestroy (font); FcPatternDestroy (matched); return 1; } // check if the data is valid if (header->Units_Per_EM == 0 || (os2->usWinAscent + os2->usWinDescent) == 0) { FT_Done_Face(face); FT_Done_FreeType(library); FcPatternDestroy (font); FcPatternDestroy (matched); return 1; } // compute font height stretch // font_size * font_stretch = windows_font_height qreal height = os2->usWinAscent + os2->usWinDescent; height = height * (2048 / header->Units_Per_EM); stretch = (1.215 * height)/2500; stretch = (1.15 * height)/2500; // seems a better guess but probably not right FT_Done_Face(face); FT_Done_FreeType(library); FcPatternDestroy (font); FcPatternDestroy (matched); textScaleMap.insert(fontFamily, stretch); #else Q_UNUSED(fontFamily); #endif //SHOULD_BUILD_FONT_CONVERSION return stretch; } KoCharacterStyle::KoCharacterStyle(QObject *parent) : QObject(parent), d(new Private()) { } KoCharacterStyle::KoCharacterStyle(const QTextCharFormat &format, QObject *parent) : QObject(parent), d(new Private()) { copyProperties(format); } KoCharacterStyle::Type KoCharacterStyle::styleType() const { return KoCharacterStyle::CharacterStyle; } void KoCharacterStyle::copyProperties(const KoCharacterStyle *style) { d->stylesPrivate = style->d->stylesPrivate; setName(style->name()); // make sure we emit property change d->parentStyle = style->d->parentStyle; d->defaultStyle = style->d->defaultStyle; } void KoCharacterStyle::copyProperties(const QTextCharFormat &format) { d->stylesPrivate = format.properties(); } KoCharacterStyle *KoCharacterStyle::clone(QObject *parent) const { KoCharacterStyle *newStyle = new KoCharacterStyle(parent); newStyle->copyProperties(this); return newStyle; } KoCharacterStyle::~KoCharacterStyle() { delete d; } void KoCharacterStyle::setDefaultStyle(KoCharacterStyle *defaultStyle) { d->defaultStyle = defaultStyle; } void KoCharacterStyle::setParentStyle(KoCharacterStyle *parent) { d->parentStyle = parent; } KoCharacterStyle *KoCharacterStyle::parentStyle() const { return d->parentStyle; } QPen KoCharacterStyle::textOutline() const { QVariant variant = value(QTextFormat::TextOutline); if (variant.isNull()) { return QPen(Qt::NoPen); } return qvariant_cast(variant); } QBrush KoCharacterStyle::background() const { QVariant variant = value(QTextFormat::BackgroundBrush); if (variant.isNull()) { return QBrush(); } return qvariant_cast(variant); } void KoCharacterStyle::clearBackground() { d->stylesPrivate.remove(QTextCharFormat::BackgroundBrush); } QBrush KoCharacterStyle::foreground() const { QVariant variant = value(QTextFormat::ForegroundBrush); if (variant.isNull()) { return QBrush(); } return qvariant_cast(variant); } void KoCharacterStyle::clearForeground() { d->stylesPrivate.remove(QTextCharFormat::ForegroundBrush); } void KoCharacterStyle::applyStyle(QTextCharFormat &format, bool emitSignal) const { if (d->parentStyle) { d->parentStyle->applyStyle(format); } bool fontSizeSet = false; // if this style has already set size don't apply the relatives const QMap props = d->stylesPrivate.properties(); QMap::const_iterator it = props.begin(); QList clearProperty; while (it != props.end()) { if (!it.value().isNull()) { if (it.key() == KoCharacterStyle::PercentageFontSize && !fontSizeSet) { qreal size = it.value().toDouble() / 100.0; if (format.hasProperty(QTextFormat::FontPointSize)) { size *= format.doubleProperty(QTextFormat::FontPointSize); } else { size *= 12.0; } format.setProperty(QTextFormat::FontPointSize, size); } else if (it.key() == KoCharacterStyle::AdditionalFontSize && !fontSizeSet) { qreal size = it.value().toDouble() / 100.0; if (format.hasProperty(QTextFormat::FontPointSize)) { size += format.doubleProperty(QTextFormat::FontPointSize); } else { size += 12.0; } format.setProperty(QTextFormat::FontPointSize, size); } else if (it.key() == QTextFormat::FontFamily) { if (!props.contains(QTextFormat::FontStyleHint)) { clearProperty.append(QTextFormat::FontStyleHint); } if (!props.contains(QTextFormat::FontFixedPitch)) { clearProperty.append(QTextFormat::FontFixedPitch); } if (!props.contains(KoCharacterStyle::FontCharset)) { clearProperty.append(KoCharacterStyle::FontCharset); } format.setProperty(it.key(), it.value()); } else { debugText << "setProperty" << it.key() << it.value(); format.setProperty(it.key(), it.value()); } if (it.key() == QTextFormat::FontPointSize) { fontSizeSet = true; } if (it.key() == QTextFormat::ForegroundBrush) { clearProperty.append(KoCharacterStyle::UseWindowFontColor); } else if (it.key() == KoCharacterStyle::UseWindowFontColor) { clearProperty.append(QTextFormat::ForegroundBrush); } } ++it; } foreach (int property, clearProperty) { debugText << "clearProperty" << property; format.clearProperty(property); } if (emitSignal) { emit styleApplied(this); d->m_inUse = true; } } KoCharacterStyle *KoCharacterStyle::autoStyle(const QTextCharFormat &format, QTextCharFormat blockCharFormat) const { KoCharacterStyle *autoStyle = new KoCharacterStyle(format); applyStyle(blockCharFormat, false); ensureMinimalProperties(blockCharFormat); autoStyle->removeDuplicates(blockCharFormat); autoStyle->setParentStyle(const_cast(this)); // remove StyleId if it is there as it is not a property of the style itself and will not be written out // so it should not be part of the autostyle. As otherwise it can happen that the StyleId is the only // property left and then we write out an empty style which is unneeded. // we also need to remove the properties of links as they are saved differently autoStyle->d->stylesPrivate.remove(StyleId); autoStyle->d->stylesPrivate.remove(QTextFormat::IsAnchor); autoStyle->d->stylesPrivate.remove(QTextFormat::AnchorHref); autoStyle->d->stylesPrivate.remove(QTextFormat::AnchorName); return autoStyle; } struct FragmentData { FragmentData(const QTextCharFormat &format, int position, int length) : format(format) , position(position) , length(length) {} QTextCharFormat format; int position; int length; }; void KoCharacterStyle::applyStyle(QTextBlock &block) const { QTextCursor cursor(block); QTextCharFormat cf = block.charFormat(); if (!cf.isTableCellFormat()) { cf = KoTextDocument(block.document()).frameCharFormat(); } applyStyle(cf); ensureMinimalProperties(cf); cursor.setBlockCharFormat(cf); // be sure that we keep the InlineInstanceId, anchor information and ChangeTrackerId when applying a style QList fragments; for (QTextBlock::iterator it = block.begin(); it != block.end(); ++it) { QTextFragment currentFragment = it.fragment(); if (currentFragment.isValid()) { QTextCharFormat format(cf); QVariant v = currentFragment.charFormat().property(InlineInstanceId); if (!v.isNull()) { format.setProperty(InlineInstanceId, v); } v = currentFragment.charFormat().property(ChangeTrackerId); if (!v.isNull()) { format.setProperty(ChangeTrackerId, v); } if (currentFragment.charFormat().isAnchor()) { format.setAnchor(true); format.setAnchorHref(currentFragment.charFormat().anchorHref()); } fragments.append(FragmentData(format, currentFragment.position(), currentFragment.length())); } } foreach (const FragmentData &fragment, fragments) { cursor.setPosition(fragment.position); cursor.setPosition(fragment.position + fragment.length, QTextCursor::KeepAnchor); cursor.setCharFormat(fragment.format); } } void KoCharacterStyle::applyStyle(QTextCursor *selection) const { // FIXME below should be done for each frament in the selection QTextCharFormat cf = selection->charFormat(); applyStyle(cf); ensureMinimalProperties(cf); selection->setCharFormat(cf); } void KoCharacterStyle::unapplyStyle(QTextCharFormat &format) const { if (d->parentStyle) d->parentStyle->unapplyStyle(format); QMap props = d->stylesPrivate.properties(); QMap::const_iterator it = props.constBegin(); while (it != props.constEnd()) { if (!it.value().isNull() && it.value() == format.property(it.key())) { format.clearProperty(it.key()); } ++it; } props = d->hardCodedDefaultStyle.properties(); it = props.constBegin(); while (it != props.constEnd()) { if (!it.value().isNull() && !format.hasProperty(it.key())) { format.setProperty(it.key(), it.value()); } ++it; } } bool KoCharacterStyle::isApplied() const { return d->m_inUse; } void KoCharacterStyle::unapplyStyle(QTextBlock &block) const { QTextCursor cursor(block); QTextCharFormat cf = cursor.blockCharFormat(); unapplyStyle(cf); cursor.setBlockCharFormat(cf); if (block.length() == 1) // only the linefeed return; QTextBlock::iterator iter = block.end(); do { --iter; QTextFragment fragment = iter.fragment(); cursor.setPosition(fragment.position() + 1); cf = cursor.charFormat(); unapplyStyle(cf); cursor.setPosition(fragment.position()); cursor.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor); cursor.setCharFormat(cf); } while (iter != block.begin()); } // OASIS 14.2.29 static void parseOdfLineWidth(const QString &width, KoCharacterStyle::LineWeight &lineWeight, qreal &lineWidth) { lineWidth = 0; lineWeight = KoCharacterStyle::AutoLineWeight; if (width.isEmpty() || width == "auto") lineWeight = KoCharacterStyle::AutoLineWeight; else if (width == "normal") lineWeight = KoCharacterStyle::NormalLineWeight; else if (width == "bold") lineWeight = KoCharacterStyle::BoldLineWeight; else if (width == "thin") lineWeight = KoCharacterStyle::ThinLineWeight; else if (width == "dash") lineWeight = KoCharacterStyle::DashLineWeight; else if (width == "medium") lineWeight = KoCharacterStyle::MediumLineWeight; else if (width == "thick") lineWeight = KoCharacterStyle::ThickLineWeight; else if (width.endsWith('%')) { lineWeight = KoCharacterStyle::PercentLineWeight; lineWidth = width.mid(0, width.length() - 1).toDouble(); } else if (width[width.length()-1].isNumber()) { lineWeight = KoCharacterStyle::LengthLineWeight; lineWidth = width.toDouble(); } else { lineWeight = KoCharacterStyle::LengthLineWeight; lineWidth = KoUnit::parseValue(width); } } // OASIS 14.2.29 static void importOdfLine(const QString &type, const QString &style, KoCharacterStyle::LineStyle &lineStyle, KoCharacterStyle::LineType &lineType) { lineStyle = KoCharacterStyle::NoLineStyle; lineType = KoCharacterStyle::NoLineType; QString fixedType = type; QString fixedStyle = style; if (fixedStyle == "none") fixedType.clear(); else if (fixedType.isEmpty() && !fixedStyle.isEmpty()) fixedType = "single"; else if (!fixedType.isEmpty() && fixedType != "none" && fixedStyle.isEmpty()) { // don't set a style when the type is none fixedStyle = "solid"; } if (fixedType == "single") lineType = KoCharacterStyle::SingleLine; else if (fixedType == "double") lineType = KoCharacterStyle::DoubleLine; if (fixedStyle == "solid") lineStyle = KoCharacterStyle::SolidLine; else if (fixedStyle == "dotted") lineStyle = KoCharacterStyle::DottedLine; else if (fixedStyle == "dash") lineStyle = KoCharacterStyle::DashLine; else if (fixedStyle == "long-dash") lineStyle = KoCharacterStyle::LongDashLine; else if (fixedStyle == "dot-dash") lineStyle = KoCharacterStyle::DotDashLine; else if (fixedStyle == "dot-dot-dash") lineStyle = KoCharacterStyle::DotDotDashLine; else if (fixedStyle == "wave") lineStyle = KoCharacterStyle::WaveLine; } static QString exportOdfLineType(KoCharacterStyle::LineType lineType) { switch (lineType) { case KoCharacterStyle::NoLineType: return "none"; case KoCharacterStyle::SingleLine: return "single"; case KoCharacterStyle::DoubleLine: return "double"; default: return ""; } } static QString exportOdfLineStyle(KoCharacterStyle::LineStyle lineStyle) { switch (lineStyle) { case KoCharacterStyle::NoLineStyle: return "none"; case KoCharacterStyle::SolidLine: return "solid"; case KoCharacterStyle::DottedLine: return "dotted"; case KoCharacterStyle::DashLine: return "dash"; case KoCharacterStyle::LongDashLine: return "long-dash"; case KoCharacterStyle::DotDashLine: return "dot-dash"; case KoCharacterStyle::DotDotDashLine: return "dot-dot-dash"; case KoCharacterStyle::WaveLine: return "wave"; default: return ""; } } static QString exportOdfLineMode(KoCharacterStyle::LineMode lineMode) { switch (lineMode) { case KoCharacterStyle::ContinuousLineMode: return "continuous"; case KoCharacterStyle::SkipWhiteSpaceLineMode: return "skip-white-space"; default: return ""; } } static QString exportOdfLineWidth(KoCharacterStyle::LineWeight lineWeight, qreal lineWidth) { switch (lineWeight) { case KoCharacterStyle::AutoLineWeight: return "auto"; case KoCharacterStyle::NormalLineWeight: return "normal"; case KoCharacterStyle::BoldLineWeight: return "bold"; case KoCharacterStyle::ThinLineWeight: return "thin"; case KoCharacterStyle::DashLineWeight: return "dash"; case KoCharacterStyle::MediumLineWeight: return "medium"; case KoCharacterStyle::ThickLineWeight: return "thick"; case KoCharacterStyle::PercentLineWeight: return QString("%1%").arg(lineWidth); case KoCharacterStyle::LengthLineWeight: return QString("%1pt").arg(lineWidth); default: return QString(); } } static QString exportOdfFontStyleHint(QFont::StyleHint hint) { switch (hint) { case QFont::Serif: return "roman"; case QFont::SansSerif: return "swiss"; case QFont::TypeWriter: return "modern"; case QFont::Decorative: return "decorative"; case QFont::System: return "system"; /*case QFont::Script */ default: return ""; } } void KoCharacterStyle::setFontFamily(const QString &family) { d->setProperty(QTextFormat::FontFamily, family); setFontYStretch(d->calculateFontYStretch(family)); } QString KoCharacterStyle::fontFamily() const { return d->propertyString(QTextFormat::FontFamily); } void KoCharacterStyle::setFontPointSize(qreal size) { d->setProperty(QTextFormat::FontPointSize, size); } void KoCharacterStyle::clearFontPointSize() { d->stylesPrivate.remove(QTextFormat::FontPointSize); } qreal KoCharacterStyle::fontPointSize() const { return d->propertyDouble(QTextFormat::FontPointSize); } void KoCharacterStyle::setFontWeight(int weight) { d->setProperty(QTextFormat::FontWeight, weight); } int KoCharacterStyle::fontWeight() const { return d->propertyInt(QTextFormat::FontWeight); } void KoCharacterStyle::setFontItalic(bool italic) { d->setProperty(QTextFormat::FontItalic, italic); } bool KoCharacterStyle::fontItalic() const { return d->propertyBoolean(QTextFormat::FontItalic); } ///TODO Review legacy fontOverline functions and testing (consider removal) /* void KoCharacterStyle::setFontOverline(bool overline) { d->setProperty(QTextFormat::FontOverline, overline); } bool KoCharacterStyle::fontOverline() const { return d->propertyBoolean(QTextFormat::FontOverline); } */ void KoCharacterStyle::setFontFixedPitch(bool fixedPitch) { d->setProperty(QTextFormat::FontFixedPitch, fixedPitch); } bool KoCharacterStyle::fontFixedPitch() const { return d->propertyBoolean(QTextFormat::FontFixedPitch); } void KoCharacterStyle::setFontStyleHint(QFont::StyleHint styleHint) { d->setProperty(QTextFormat::FontStyleHint, styleHint); } QFont::StyleHint KoCharacterStyle::fontStyleHint() const { return static_cast(d->propertyInt(QTextFormat::FontStyleHint)); } void KoCharacterStyle::setFontKerning(bool enable) { d->setProperty(QTextFormat::FontKerning, enable); } bool KoCharacterStyle::fontKerning() const { return d->propertyBoolean(QTextFormat::FontKerning); } void KoCharacterStyle::setVerticalAlignment(QTextCharFormat::VerticalAlignment alignment) { d->setProperty(QTextFormat::TextVerticalAlignment, alignment); } QTextCharFormat::VerticalAlignment KoCharacterStyle::verticalAlignment() const { return static_cast(d->propertyInt(QTextFormat::TextVerticalAlignment)); } void KoCharacterStyle::setTextOutline(const QPen &pen) { d->setProperty(QTextFormat::TextOutline, pen); } void KoCharacterStyle::setBackground(const QBrush &brush) { d->setProperty(QTextFormat::BackgroundBrush, brush); } void KoCharacterStyle::setForeground(const QBrush &brush) { d->setProperty(QTextFormat::ForegroundBrush, brush); } void KoCharacterStyle::setFontAutoColor(bool use) { d->setProperty(KoCharacterStyle::UseWindowFontColor, use); } QString KoCharacterStyle::name() const { return d->name; } void KoCharacterStyle::setName(const QString &name) { if (name == d->name) return; d->name = name; emit nameChanged(name); } int KoCharacterStyle::styleId() const { return d->propertyInt(StyleId); } void KoCharacterStyle::setStyleId(int id) { d->setProperty(StyleId, id); } QFont KoCharacterStyle::font() const { QFont font; if (d->stylesPrivate.contains(QTextFormat::FontFamily)) font.setFamily(fontFamily()); if (d->stylesPrivate.contains(QTextFormat::FontPointSize)) font.setPointSizeF(fontPointSize()); if (d->stylesPrivate.contains(QTextFormat::FontWeight)) font.setWeight(fontWeight()); if (d->stylesPrivate.contains(QTextFormat::FontItalic)) font.setItalic(fontItalic()); return font; } void KoCharacterStyle::setHasHyphenation(bool on) { d->setProperty(HasHyphenation, on); } bool KoCharacterStyle::hasHyphenation() const { return d->propertyBoolean(HasHyphenation); } void KoCharacterStyle::setHyphenationPushCharCount(int count) { if (count > 0) d->setProperty(HyphenationPushCharCount, count); else d->stylesPrivate.remove(HyphenationPushCharCount); } int KoCharacterStyle::hyphenationPushCharCount() const { if (hasProperty(HyphenationPushCharCount)) return d->propertyInt(HyphenationPushCharCount); return 0; } void KoCharacterStyle::setHyphenationRemainCharCount(int count) { if (count > 0) d->setProperty(HyphenationRemainCharCount, count); else d->stylesPrivate.remove(HyphenationRemainCharCount); } int KoCharacterStyle::hyphenationRemainCharCount() const { if (hasProperty(HyphenationRemainCharCount)) return d->propertyInt(HyphenationRemainCharCount); return 0; } void KoCharacterStyle::setStrikeOutStyle(KoCharacterStyle::LineStyle strikeOut) { d->setProperty(StrikeOutStyle, strikeOut); } KoCharacterStyle::LineStyle KoCharacterStyle::strikeOutStyle() const { return (KoCharacterStyle::LineStyle) d->propertyInt(StrikeOutStyle); } void KoCharacterStyle::setStrikeOutType(LineType lineType) { d->setProperty(StrikeOutType, lineType); } KoCharacterStyle::LineType KoCharacterStyle::strikeOutType() const { return (KoCharacterStyle::LineType) d->propertyInt(StrikeOutType); } void KoCharacterStyle::setStrikeOutColor(const QColor &color) { d->setProperty(StrikeOutColor, color); } QColor KoCharacterStyle::strikeOutColor() const { return d->propertyColor(StrikeOutColor); } void KoCharacterStyle::setStrikeOutWidth(LineWeight weight, qreal width) { d->setProperty(KoCharacterStyle::StrikeOutWeight, weight); d->setProperty(KoCharacterStyle::StrikeOutWidth, width); } void KoCharacterStyle::strikeOutWidth(LineWeight &weight, qreal &width) const { weight = (KoCharacterStyle::LineWeight) d->propertyInt(KoCharacterStyle::StrikeOutWeight); width = d->propertyDouble(KoCharacterStyle::StrikeOutWidth); } void KoCharacterStyle::setStrikeOutMode(LineMode lineMode) { d->setProperty(StrikeOutMode, lineMode); } void KoCharacterStyle::setStrikeOutText(const QString &text) { d->setProperty(StrikeOutText, text); } QString KoCharacterStyle::strikeOutText() const { return d->propertyString(StrikeOutText); } KoCharacterStyle::LineMode KoCharacterStyle::strikeOutMode() const { return (KoCharacterStyle::LineMode) d->propertyInt(StrikeOutMode); } void KoCharacterStyle::setOverlineStyle(KoCharacterStyle::LineStyle overline) { d->setProperty(OverlineStyle, overline); } KoCharacterStyle::LineStyle KoCharacterStyle::overlineStyle() const { return (KoCharacterStyle::LineStyle) d->propertyInt(OverlineStyle); } void KoCharacterStyle::setOverlineType(LineType lineType) { d->setProperty(OverlineType, lineType); } KoCharacterStyle::LineType KoCharacterStyle::overlineType() const { return (KoCharacterStyle::LineType) d->propertyInt(OverlineType); } void KoCharacterStyle::setOverlineColor(const QColor &color) { d->setProperty(KoCharacterStyle::OverlineColor, color); } QColor KoCharacterStyle::overlineColor() const { return d->propertyColor(KoCharacterStyle::OverlineColor); } void KoCharacterStyle::setOverlineWidth(LineWeight weight, qreal width) { d->setProperty(KoCharacterStyle::OverlineWeight, weight); d->setProperty(KoCharacterStyle::OverlineWidth, width); } void KoCharacterStyle::overlineWidth(LineWeight &weight, qreal &width) const { weight = (KoCharacterStyle::LineWeight) d->propertyInt(KoCharacterStyle::OverlineWeight); width = d->propertyDouble(KoCharacterStyle::OverlineWidth); } void KoCharacterStyle::setOverlineMode(LineMode mode) { d->setProperty(KoCharacterStyle::OverlineMode, mode); } KoCharacterStyle::LineMode KoCharacterStyle::overlineMode() const { return static_cast(d->propertyInt(KoCharacterStyle::OverlineMode)); } void KoCharacterStyle::setUnderlineStyle(KoCharacterStyle::LineStyle underline) { d->setProperty(UnderlineStyle, underline); } KoCharacterStyle::LineStyle KoCharacterStyle::underlineStyle() const { return (KoCharacterStyle::LineStyle) d->propertyInt(UnderlineStyle); } void KoCharacterStyle::setUnderlineType(LineType lineType) { d->setProperty(UnderlineType, lineType); } KoCharacterStyle::LineType KoCharacterStyle::underlineType() const { return (KoCharacterStyle::LineType) d->propertyInt(UnderlineType); } void KoCharacterStyle::setUnderlineColor(const QColor &color) { d->setProperty(QTextFormat::TextUnderlineColor, color); } QColor KoCharacterStyle::underlineColor() const { return d->propertyColor(QTextFormat::TextUnderlineColor); } void KoCharacterStyle::setUnderlineWidth(LineWeight weight, qreal width) { d->setProperty(KoCharacterStyle::UnderlineWeight, weight); d->setProperty(KoCharacterStyle::UnderlineWidth, width); } void KoCharacterStyle::underlineWidth(LineWeight &weight, qreal &width) const { weight = (KoCharacterStyle::LineWeight) d->propertyInt(KoCharacterStyle::UnderlineWeight); width = d->propertyDouble(KoCharacterStyle::UnderlineWidth); } void KoCharacterStyle::setUnderlineMode(LineMode mode) { d->setProperty(KoCharacterStyle::UnderlineMode, mode); } KoCharacterStyle::LineMode KoCharacterStyle::underlineMode() const { return static_cast(d->propertyInt(KoCharacterStyle::UnderlineMode)); } void KoCharacterStyle::setFontLetterSpacing(qreal spacing) { d->setProperty(KoCharacterStyle::FontLetterSpacing, spacing); } qreal KoCharacterStyle::fontLetterSpacing() const { return d->propertyDouble(KoCharacterStyle::FontLetterSpacing); } void KoCharacterStyle::setFontWordSpacing(qreal spacing) { d->setProperty(QTextCharFormat::FontWordSpacing, spacing); } qreal KoCharacterStyle::fontWordSpacing() const { return d->propertyDouble(QTextCharFormat::FontWordSpacing); } void KoCharacterStyle::setFontCapitalization(QFont::Capitalization capitalization) { d->setProperty(QTextFormat::FontCapitalization, capitalization); } QFont::Capitalization KoCharacterStyle::fontCapitalization() const { return (QFont::Capitalization) d->propertyInt(QTextFormat::FontCapitalization); } void KoCharacterStyle::setFontYStretch(qreal stretch) { d->setProperty(KoCharacterStyle::FontYStretch, stretch); } qreal KoCharacterStyle::fontYStretch() const { return d->propertyDouble(KoCharacterStyle::FontYStretch); } void KoCharacterStyle::setCountry(const QString &country) { if (country.isEmpty()) d->stylesPrivate.remove(KoCharacterStyle::Country); else d->setProperty(KoCharacterStyle::Country, country); } void KoCharacterStyle::setLanguage(const QString &language) { if (language.isEmpty()) d->stylesPrivate.remove(KoCharacterStyle::Language); else d->setProperty(KoCharacterStyle::Language, language); } QString KoCharacterStyle::country() const { return value(KoCharacterStyle::Country).toString(); } QString KoCharacterStyle::language() const { return d->propertyString(KoCharacterStyle::Language); } bool KoCharacterStyle::blinking() const { return d->propertyBoolean(Blink); } void KoCharacterStyle::setBlinking(bool blink) { d->setProperty(KoCharacterStyle::Blink, blink); } bool KoCharacterStyle::hasProperty(int key) const { return d->stylesPrivate.contains(key); } static QString rotationScaleToString(KoCharacterStyle::RotationScale rotationScale) { QString scale = "line-height"; if (rotationScale == KoCharacterStyle::Fixed) { scale = "fixed"; } return scale; } static KoCharacterStyle::RotationScale stringToRotationScale(const QString &scale) { KoCharacterStyle::RotationScale rotationScale = KoCharacterStyle::LineHeight; if (scale == "fixed") { rotationScale = KoCharacterStyle::Fixed; } return rotationScale; } void KoCharacterStyle::setTextRotationAngle(qreal angle) { d->setProperty(TextRotationAngle, angle); } qreal KoCharacterStyle::textRotationAngle() const { return d->propertyDouble(TextRotationAngle); } void KoCharacterStyle::setTextRotationScale(RotationScale scale) { d->setProperty(TextRotationScale, rotationScaleToString(scale)); } KoCharacterStyle::RotationScale KoCharacterStyle::textRotationScale() const { return stringToRotationScale(d->propertyString(TextRotationScale)); } void KoCharacterStyle::setTextScale(int scale) { d->setProperty(TextScale, scale); } int KoCharacterStyle::textScale() const { return d->propertyInt(TextScale); } void KoCharacterStyle::setTextShadow(const KoShadowStyle& shadow) { d->setProperty(TextShadow, qVariantFromValue(shadow)); } KoShadowStyle KoCharacterStyle::textShadow() const { if (hasProperty(TextShadow)) { QVariant shadow = value(TextShadow); if (shadow.canConvert()) return shadow.value(); } return KoShadowStyle(); } void KoCharacterStyle::setTextCombine(KoCharacterStyle::TextCombineType type) { d->setProperty(TextCombine, type); } KoCharacterStyle::TextCombineType KoCharacterStyle::textCombine() const { if (hasProperty(TextCombine)) { return (KoCharacterStyle::TextCombineType) d->propertyInt(TextCombine); } return NoTextCombine; } QChar KoCharacterStyle::textCombineEndChar() const { if (hasProperty(TextCombineEndChar)) { QString val = d->propertyString(TextCombineEndChar); if (val.length() > 0) return val.at(0); } return QChar(); } void KoCharacterStyle::setTextCombineEndChar(const QChar& character) { d->setProperty(TextCombineEndChar, character); } QChar KoCharacterStyle::textCombineStartChar() const { if (hasProperty(TextCombineStartChar)) { QString val = d->propertyString(TextCombineStartChar); if (val.length() > 0) return val.at(0); } return QChar(); } void KoCharacterStyle::setTextCombineStartChar(const QChar& character) { d->setProperty(TextCombineStartChar, character); } void KoCharacterStyle::setFontRelief(KoCharacterStyle::ReliefType relief) { d->setProperty(FontRelief, relief); } KoCharacterStyle::ReliefType KoCharacterStyle::fontRelief() const { if (hasProperty(FontRelief)) return (KoCharacterStyle::ReliefType) d->propertyInt(FontRelief); return KoCharacterStyle::NoRelief; } KoCharacterStyle::EmphasisPosition KoCharacterStyle::textEmphasizePosition() const { if (hasProperty(TextEmphasizePosition)) return (KoCharacterStyle::EmphasisPosition) d->propertyInt(TextEmphasizePosition); return KoCharacterStyle::EmphasisAbove; } void KoCharacterStyle::setTextEmphasizePosition(KoCharacterStyle::EmphasisPosition position) { d->setProperty(TextEmphasizePosition, position); } KoCharacterStyle::EmphasisStyle KoCharacterStyle::textEmphasizeStyle() const { if (hasProperty(TextEmphasizeStyle)) return (KoCharacterStyle::EmphasisStyle) d->propertyInt(TextEmphasizeStyle); return KoCharacterStyle::NoEmphasis; } void KoCharacterStyle::setTextEmphasizeStyle(KoCharacterStyle::EmphasisStyle emphasis) { d->setProperty(TextEmphasizeStyle, emphasis); } void KoCharacterStyle::setPercentageFontSize(qreal percent) { d->setProperty(KoCharacterStyle::PercentageFontSize, percent); } qreal KoCharacterStyle::percentageFontSize() const { return d->propertyDouble(KoCharacterStyle::PercentageFontSize); } void KoCharacterStyle::setAdditionalFontSize(qreal percent) { d->setProperty(KoCharacterStyle::AdditionalFontSize, percent); } qreal KoCharacterStyle::additionalFontSize() const { return d->propertyDouble(KoCharacterStyle::AdditionalFontSize); } void KoCharacterStyle::loadOdf(const KoXmlElement *element, KoShapeLoadingContext &scontext, bool loadParents) { KoOdfLoadingContext &context = scontext.odfLoadingContext(); const QString name(element->attributeNS(KoXmlNS::style, "display-name", QString())); if (!name.isEmpty()) { d->name = name; } else { d->name = element->attributeNS(KoXmlNS::style, "name", QString()); } QString family = element->attributeNS(KoXmlNS::style, "family", "text"); context.styleStack().save(); if (loadParents) { context.addStyles(element, family.toLocal8Bit().constData()); // Load all parent } else { context.styleStack().push(*element); } context.styleStack().setTypeProperties("text"); // load the style:text-properties loadOdfProperties(scontext); context.styleStack().restore(); } void KoCharacterStyle::loadOdfProperties(KoShapeLoadingContext &scontext) { KoStyleStack &styleStack = scontext.odfLoadingContext().styleStack(); d->stylesPrivate = StylePrivate(); // The fo:color attribute specifies the foreground color of text. const QString color(styleStack.property(KoXmlNS::fo, "color")); if (!color.isEmpty()) { QColor c(color); if (c.isValid()) { // 3.10.3 setForeground(QBrush(c)); } } QString fontName(styleStack.property(KoXmlNS::fo, "font-family")); if (!fontName.isEmpty()) { // Specify whether a font has a fixed or variable width. // These attributes are ignored if there is no corresponding fo:font-family attribute attached to the same formatting properties element. const QString fontPitch(styleStack.property(KoXmlNS::style, "font-pitch")); if (!fontPitch.isEmpty()) { setFontFixedPitch(fontPitch == "fixed"); } const QString genericFamily(styleStack.property(KoXmlNS::style, "font-family-generic")); if (!genericFamily.isEmpty()) { if (genericFamily == "roman") setFontStyleHint(QFont::Serif); else if (genericFamily == "swiss") setFontStyleHint(QFont::SansSerif); else if (genericFamily == "modern") setFontStyleHint(QFont::TypeWriter); else if (genericFamily == "decorative") setFontStyleHint(QFont::Decorative); else if (genericFamily == "system") setFontStyleHint(QFont::System); else if (genericFamily == "script") { ; // TODO: no hint available in Qt yet, we should at least store it as a property internally! } } const QString fontCharset(styleStack.property(KoXmlNS::style, "font-charset")); if (!fontCharset.isEmpty()) { // this property is not required by Qt, since Qt auto selects the right font based on the text // The only charset of interest to us is x-symbol - this should disable spell checking d->setProperty(KoCharacterStyle::FontCharset, fontCharset); } } const QString fontFamily(styleStack.property(KoXmlNS::style, "font-family")); if (!fontFamily.isEmpty()) fontName = fontFamily; if (styleStack.hasProperty(KoXmlNS::style, "font-name")) { // This font name is a reference to a font face declaration. KoOdfStylesReader &stylesReader = scontext.odfLoadingContext().stylesReader(); const KoXmlElement *fontFace = stylesReader.findStyle(styleStack.property(KoXmlNS::style, "font-name")); if (fontFace != 0) { fontName = fontFace->attributeNS(KoXmlNS::svg, "font-family", ""); KoXmlElement fontFaceElem; forEachElement(fontFaceElem, (*fontFace)) { if (fontFaceElem.tagName() == "font-face-src") { KoXmlElement fontUriElem; forEachElement(fontUriElem, fontFaceElem) { if (fontUriElem.tagName() == "font-face-uri") { QString filename = fontUriElem.attributeNS(KoXmlNS::xlink, "href"); KoStore *store = scontext.odfLoadingContext().store(); if (store->open(filename)) { KoStoreDevice device(store); QByteArray data = device.readAll(); if (device.open(QIODevice::ReadOnly)) { QFontDatabase::addApplicationFontFromData(data); } } } } } } } } if (!fontName.isEmpty()) { // Hmm, the remove "'" could break it's in the middle of the fontname... fontName = fontName.remove('\''); // 'Thorndale' is not known outside OpenOffice so we substitute it // with 'Times New Roman' that looks nearly the same. if (fontName == "Thorndale") fontName = "Times New Roman"; // 'StarSymbol' is written by OpenOffice but they actually mean // 'OpenSymbol'. if (fontName == "StarSymbol") fontName = "OpenSymbol"; fontName.remove(QRegExp("\\sCE$")); // Arial CE -> Arial setFontFamily(fontName); } // Specify the size of a font. The value of these attribute is either an absolute length or a percentage if (styleStack.hasProperty(KoXmlNS::fo, "font-size")) { const QString fontSize(styleStack.property(KoXmlNS::fo, "font-size")); if (!fontSize.isEmpty()) { if (fontSize.endsWith('%')) { setPercentageFontSize(fontSize.left(fontSize.length() - 1).toDouble()); } else { setFontPointSize(KoUnit::parseValue(fontSize)); } } } else { const QString fontSizeRel(styleStack.property(KoXmlNS::style, "font-size-rel")); if (!fontSizeRel.isEmpty()) { setAdditionalFontSize(KoUnit::parseValue(fontSizeRel)); } } // Specify the weight of a font. The permitted values are normal, bold, and numeric values 100-900, in steps of 100. Unsupported numerical values are rounded off to the next supported value. const QString fontWeight(styleStack.property(KoXmlNS::fo, "font-weight")); if (!fontWeight.isEmpty()) { // 3.10.24 int boldness; if (fontWeight == "normal") boldness = 50; else if (fontWeight == "bold") boldness = 75; else // XSL/CSS has 100,200,300...900. Not the same scale as Qt! // See http://www.w3.org/TR/2001/REC-xsl-20011015/slice7.html#font-weight boldness = fontWeight.toInt() / 10; setFontWeight(boldness); } // Specify whether to use normal or italic font face. const QString fontStyle(styleStack.property(KoXmlNS::fo, "font-style" )); if (!fontStyle.isEmpty()) { // 3.10.19 if (fontStyle == "italic" || fontStyle == "oblique") { // no difference in kotext setFontItalic(true); } else { setFontItalic(false); } } //TODO #if 0 d->m_bWordByWord = styleStack.property(KoXmlNS::style, "text-underline-mode") == "skip-white-space"; // TODO style:text-line-through-mode /* // OO compat code, to move to OO import filter d->m_bWordByWord = (styleStack.hasProperty( KoXmlNS::fo, "score-spaces")) // 3.10.25 && (styleStack.property( KoXmlNS::fo, "score-spaces") == "false"); if( styleStack.hasProperty( KoXmlNS::style, "text-crossing-out" )) { // 3.10.6 QString strikeOutType = styleStack.property( KoXmlNS::style, "text-crossing-out" ); if( strikeOutType =="double-line") m_strikeOutType = S_DOUBLE; else if( strikeOutType =="single-line") m_strikeOutType = S_SIMPLE; else if( strikeOutType =="thick-line") m_strikeOutType = S_SIMPLE_BOLD; // not supported by Words: "slash" and "X" // not supported by OO: stylelines (solid, dash, dot, dashdot, dashdotdot) } */ #endif // overline modes const QString textOverlineMode(styleStack.property( KoXmlNS::style, "text-overline-mode")); if (!textOverlineMode.isEmpty()) { if (textOverlineMode == "skip-white-space") { setOverlineMode(SkipWhiteSpaceLineMode); } else if (textOverlineMode == "continuous") { setOverlineMode(ContinuousLineMode); } } // Specifies whether text is overlined, and if so, whether a single or qreal line will be used for overlining. const QString textOverlineType(styleStack.property(KoXmlNS::style, "text-overline-type")); const QString textOverlineStyle(styleStack.property(KoXmlNS::style, "text-overline-style")); if (!textOverlineType.isEmpty() || !textOverlineStyle.isEmpty()) { // OASIS 14.4.28 LineStyle overlineStyle; LineType overlineType; importOdfLine(textOverlineType, textOverlineStyle, overlineStyle, overlineType); setOverlineStyle(overlineStyle); setOverlineType(overlineType); } const QString textOverlineWidth(styleStack.property(KoXmlNS::style, "text-overline-width")); if (!textOverlineWidth.isEmpty()) { qreal overlineWidth; LineWeight overlineWeight; parseOdfLineWidth(textOverlineWidth, overlineWeight, overlineWidth); setOverlineWidth(overlineWeight, overlineWidth); } // Specifies the color that is used to overline text. The value of this attribute is either font-color or a color. If the value is font-color, the current text color is used for overlining. QString overLineColor = styleStack.property(KoXmlNS::style, "text-overline-color"); // OO 3.10.23, OASIS 14.4.31 if (!overLineColor.isEmpty() && overLineColor != "font-color") { setOverlineColor(QColor(overLineColor)); } else if (overLineColor == "font-color") { setOverlineColor(QColor()); } // underline modes const QString textUndelineMode(styleStack.property( KoXmlNS::style, "text-underline-mode")); if (!textUndelineMode.isEmpty()) { if (textUndelineMode == "skip-white-space") { setUnderlineMode(SkipWhiteSpaceLineMode); } else if (textUndelineMode == "continuous") { setUnderlineMode(ContinuousLineMode); } } // Specifies whether text is underlined, and if so, whether a single or qreal line will be used for underlining. const QString textUnderlineType(styleStack.property(KoXmlNS::style, "text-underline-type")); const QString textUnderlineStyle(styleStack.property(KoXmlNS::style, "text-underline-style")); if (!textUnderlineType.isEmpty() || !textUnderlineStyle.isEmpty()) { // OASIS 14.4.28 LineStyle underlineStyle; LineType underlineType; importOdfLine(textUnderlineType, textUnderlineStyle, underlineStyle, underlineType); setUnderlineStyle(underlineStyle); setUnderlineType(underlineType); } const QString textUnderlineWidth(styleStack.property(KoXmlNS::style, "text-underline-width")); if (!textUnderlineWidth.isEmpty()) { qreal underlineWidth; LineWeight underlineWeight; parseOdfLineWidth(textUnderlineWidth, underlineWeight, underlineWidth); setUnderlineWidth(underlineWeight, underlineWidth); } // Specifies the color that is used to underline text. The value of this attribute is either font-color or a color. If the value is font-color, the current text color is used for underlining. QString underLineColor = styleStack.property(KoXmlNS::style, "text-underline-color"); // OO 3.10.23, OASIS 14.4.31 if (!underLineColor.isEmpty() && underLineColor != "font-color") { setUnderlineColor(QColor(underLineColor)); } else if (underLineColor == "font-color") { setUnderlineColor(QColor()); } const QString textLineThroughType(styleStack.property(KoXmlNS::style, "text-line-through-type")); const QString textLineThroughStyle(styleStack.property(KoXmlNS::style, "text-line-through-style")); if (!textLineThroughType.isEmpty() || !textLineThroughStyle.isEmpty()) { // OASIS 14.4.7 KoCharacterStyle::LineStyle throughStyle; LineType throughType; importOdfLine(textLineThroughType,textLineThroughStyle, throughStyle, throughType); setStrikeOutStyle(throughStyle); setStrikeOutType(throughType); const QString textLineThroughText(styleStack.property(KoXmlNS::style, "text-line-through-text")); if (!textLineThroughText.isEmpty()) { setStrikeOutText(textLineThroughText); } } const QString textLineThroughWidth(styleStack.property(KoXmlNS::style, "text-line-through-width")); if (!textLineThroughWidth.isEmpty()) { qreal throughWidth; LineWeight throughWeight; parseOdfLineWidth(textLineThroughWidth, throughWeight, throughWidth); setStrikeOutWidth(throughWeight, throughWidth); } const QString lineThroughColor(styleStack.property(KoXmlNS::style, "text-line-through-color")); // OO 3.10.23, OASIS 14.4.31 if (!lineThroughColor.isEmpty() && lineThroughColor != "font-color") { setStrikeOutColor(QColor(lineThroughColor)); } const QString lineThroughMode(styleStack.property(KoXmlNS::style, "text-line-through-mode")); if (lineThroughMode == "continuous") { setStrikeOutMode(ContinuousLineMode); } else if (lineThroughMode == "skip-white-space") { setStrikeOutMode(SkipWhiteSpaceLineMode); } const QString textPosition(styleStack.property(KoXmlNS::style, "text-position")); if (!textPosition.isEmpty()) { // OO 3.10.7 if (textPosition.startsWith("super")) setVerticalAlignment(QTextCharFormat::AlignSuperScript); else if (textPosition.startsWith("sub")) setVerticalAlignment(QTextCharFormat::AlignSubScript); else { QRegExp re("(-?[\\d.]+)%.*"); if (re.exactMatch(textPosition)) { int percent = re.capturedTexts()[1].toInt(); if (percent > 0) setVerticalAlignment(QTextCharFormat::AlignSuperScript); else if (percent < 0) setVerticalAlignment(QTextCharFormat::AlignSubScript); else // set explicit to overwrite inherited text-position's setVerticalAlignment(QTextCharFormat::AlignNormal); } } } // The fo:font-variant attribute provides the option to display text as small capitalized letters. const QString textVariant(styleStack.property(KoXmlNS::fo, "font-variant")); if (!textVariant.isEmpty()) { if (textVariant == "small-caps") setFontCapitalization(QFont::SmallCaps); else if (textVariant == "normal") setFontCapitalization(QFont::MixedCase); } // The fo:text-transform attribute specifies text transformations to uppercase, lowercase, and capitalization. else { const QString textTransform(styleStack.property(KoXmlNS::fo, "text-transform")); if (!textTransform.isEmpty()) { if (textTransform == "uppercase") setFontCapitalization(QFont::AllUppercase); else if (textTransform == "lowercase") setFontCapitalization(QFont::AllLowercase); else if (textTransform == "capitalize") setFontCapitalization(QFont::Capitalize); else if (textTransform == "none") setFontCapitalization(QFont::MixedCase); } } const QString foLanguage(styleStack.property(KoXmlNS::fo, "language")); if (!foLanguage.isEmpty()) { setLanguage(foLanguage); } const QString foCountry(styleStack.property(KoXmlNS::fo, "country")); if (!foCountry.isEmpty()) { setCountry(foCountry); } // The fo:background-color attribute specifies the background color of a paragraph. const QString bgcolor(styleStack.property(KoXmlNS::fo, "background-color")); if (!bgcolor.isEmpty()) { QBrush brush = background(); if (bgcolor == "transparent") brush.setStyle(Qt::NoBrush); else { if (brush.style() == Qt::NoBrush) brush.setStyle(Qt::SolidPattern); brush.setColor(bgcolor); // #rrggbb format } setBackground(brush); } // The style:use-window-font-color attribute specifies whether or not the window foreground color should be as used as the foreground color for a light background color and white for a dark background color. const QString useWindowFont(styleStack.property(KoXmlNS::style, "use-window-font-color")); if (!useWindowFont.isEmpty()) { setFontAutoColor(useWindowFont == "true"); } const QString letterKerning(styleStack.property( KoXmlNS::style, "letter-kerning")); if (!letterKerning.isEmpty()) { setFontKerning(letterKerning == "true"); } const QString letterSpacing(styleStack.property(KoXmlNS::fo, "letter-spacing")); if ((!letterSpacing.isEmpty()) && (letterSpacing != "normal")) { qreal space = KoUnit::parseValue(letterSpacing); setFontLetterSpacing(space); } const QString textOutline(styleStack.property(KoXmlNS::style, "text-outline")); if (!textOutline.isEmpty()) { if (textOutline == "true") { setTextOutline(QPen((foreground().style() != Qt::NoBrush)?foreground():QBrush(Qt::black) , 0)); setForeground(Qt::transparent); } else { setTextOutline(QPen(Qt::NoPen)); } } const QString textRotationAngle(styleStack.property(KoXmlNS::style, "text-rotation-angle")); if (!textRotationAngle.isEmpty()) { setTextRotationAngle(KoUnit::parseAngle(textRotationAngle)); } const QString textRotationScale(styleStack.property(KoXmlNS::style, "text-rotation-scale")); if (!textRotationScale.isEmpty()) { setTextRotationScale(stringToRotationScale(textRotationScale)); } const QString textScale(styleStack.property(KoXmlNS::style, "text-scale")); if (!textScale.isEmpty()) { const int scale = (textScale.endsWith('%') ? textScale.left(textScale.length()-1) : textScale).toInt(); setTextScale(scale); } const QString textShadow(styleStack.property(KoXmlNS::fo, "text-shadow")); if (!textShadow.isEmpty()) { KoShadowStyle shadow; if (shadow.loadOdf(textShadow)) setTextShadow(shadow); } const QString textCombine(styleStack.property(KoXmlNS::style, "text-combine")); if (!textCombine.isEmpty()) { if (textCombine == "letters") setTextCombine(TextCombineLetters); else if (textCombine == "lines") setTextCombine(TextCombineLines); else if (textCombine == "none") setTextCombine(NoTextCombine); } const QString textCombineEndChar(styleStack.property(KoXmlNS::style, "text-combine-end-char")); if (!textCombineEndChar.isEmpty()) { setTextCombineEndChar(textCombineEndChar.at(0)); } const QString textCombineStartChar(styleStack.property(KoXmlNS::style, "text-combine-start-char")); if (!textCombineStartChar.isEmpty()) { setTextCombineStartChar(textCombineStartChar.at(0)); } const QString fontRelief(styleStack.property(KoXmlNS::style, "font-relief")); if (!fontRelief.isEmpty()) { if (fontRelief == "none") setFontRelief(KoCharacterStyle::NoRelief); else if (fontRelief == "embossed") setFontRelief(KoCharacterStyle::Embossed); else if (fontRelief == "engraved") setFontRelief(KoCharacterStyle::Engraved); } const QString fontEmphasize(styleStack.property(KoXmlNS::style, "text-emphasize")); if (!fontEmphasize.isEmpty()) { QString style, position; QStringList parts = fontEmphasize.split(' '); style = parts[0]; if (parts.length() > 1) position = parts[1]; if (style == "none") { setTextEmphasizeStyle(NoEmphasis); } else if (style == "accent") { setTextEmphasizeStyle(AccentEmphasis); } else if (style == "circle") { setTextEmphasizeStyle(CircleEmphasis); } else if (style == "disc") { setTextEmphasizeStyle(DiscEmphasis); } else if (style == "dot") { setTextEmphasizeStyle(DotEmphasis); } if (position == "below") { setTextEmphasizePosition(EmphasisBelow); } else if (position == "above") { setTextEmphasizePosition(EmphasisAbove); } } if (styleStack.hasProperty(KoXmlNS::fo, "hyphenate")) setHasHyphenation(styleStack.property(KoXmlNS::fo, "hyphenate") == "true"); if (styleStack.hasProperty(KoXmlNS::fo, "hyphenation-remain-char-count")) { bool ok = false; int count = styleStack.property(KoXmlNS::fo, "hyphenation-remain-char-count").toInt(&ok); if (ok) setHyphenationRemainCharCount(count); } if (styleStack.hasProperty(KoXmlNS::fo, "hyphenation-push-char-count")) { bool ok = false; int count = styleStack.property(KoXmlNS::fo, "hyphenation-push-char-count").toInt(&ok); if (ok) setHyphenationPushCharCount(count); } if (styleStack.hasProperty(KoXmlNS::style, "text-blinking")) { setBlinking(styleStack.property(KoXmlNS::style, "text-blinking") == "true"); } //TODO #if 0 /* Missing properties: style:font-style-name, 3.10.11 - can be ignored, says DV, the other ways to specify a font are more precise fo:letter-spacing, 3.10.16 - not implemented in kotext style:text-relief, 3.10.20 - not implemented in kotext style:text-blinking, 3.10.27 - not implemented in kotext IIRC style:text-combine, 3.10.29/30 - not implemented, see http://www.w3.org/TR/WD-i18n-format/ style:text-emphasis, 3.10.31 - not implemented in kotext style:text-scale, 3.10.33 - not implemented in kotext style:text-rotation-angle, 3.10.34 - not implemented in kotext (kpr rotates whole objects) style:text-rotation-scale, 3.10.35 - not implemented in kotext (kpr rotates whole objects) style:punctuation-wrap, 3.10.36 - not implemented in kotext */ d->m_underLineWidth = 1.0; generateKey(); addRef(); #endif } bool KoCharacterStyle::operator==(const KoCharacterStyle &other) const { return compareCharacterProperties(other); } bool KoCharacterStyle::operator!=(const KoCharacterStyle &other) const { return !compareCharacterProperties(other); } bool KoCharacterStyle::compareCharacterProperties(const KoCharacterStyle &other) const { return other.d->stylesPrivate == d->stylesPrivate; } void KoCharacterStyle::removeDuplicates(const KoCharacterStyle &other) { // In case the current style doesn't have the flag UseWindowFontColor set but the other has it set and they use the same color // remove duplicates will remove the color. However to make it work correctly we need to store the color with the style so it // will be loaded again. We don't store a use-window-font-color="false" as that is not compatible to the way OO/LO does work. // So save the color and restore it after the remove duplicates QBrush brush; if (other.d->propertyBoolean(KoCharacterStyle::UseWindowFontColor) && !d->propertyBoolean(KoCharacterStyle::UseWindowFontColor)) { brush = foreground(); } // this properties should need to be kept if there is a font family defined as these are only evaluated if there is also a font family int keepProperties[] = { QTextFormat::FontStyleHint, QTextFormat::FontFixedPitch, KoCharacterStyle::FontCharset }; QMap keep; for (unsigned int i = 0; i < sizeof(keepProperties)/sizeof(*keepProperties); ++i) { if (hasProperty(keepProperties[i])) { keep.insert(keepProperties[i], value(keepProperties[i])); } } this->d->stylesPrivate.removeDuplicates(other.d->stylesPrivate); if (brush.style() != Qt::NoBrush) { setForeground(brush); } // in case the char style has any of the following properties it also needs to have the fontFamily as otherwise // these values will be ignored when loading according to the odf spec if (!hasProperty(QTextFormat::FontFamily)) { if (hasProperty(QTextFormat::FontStyleHint) || hasProperty(QTextFormat::FontFixedPitch) || hasProperty(KoCharacterStyle::FontCharset)) { QString fontFamily = other.fontFamily(); if (!fontFamily.isEmpty()) { setFontFamily(fontFamily); } } } else { for (QMap::const_iterator it(keep.constBegin()); it != keep.constEnd(); ++it) { this->d->stylesPrivate.add(it.key(), it.value()); } } } void KoCharacterStyle::removeDuplicates(const QTextCharFormat &otherFormat) { KoCharacterStyle other(otherFormat); removeDuplicates(other); } void KoCharacterStyle::remove(int key) { d->stylesPrivate.remove(key); } bool KoCharacterStyle::isEmpty() const { return d->stylesPrivate.isEmpty(); } void KoCharacterStyle::saveOdf(KoGenStyle &style) const { if (!d->name.isEmpty() && !style.isDefaultStyle()) { style.addAttribute("style:display-name", d->name); } QList keys = d->stylesPrivate.keys(); Q_FOREACH (int key, keys) { if (key == QTextFormat::FontWeight) { bool ok = false; int boldness = d->stylesPrivate.value(key).toInt(&ok); if (ok) { if (boldness == QFont::Normal) { style.addProperty("fo:font-weight", "normal", KoGenStyle::TextType); } else if (boldness == QFont::Bold) { style.addProperty("fo:font-weight", "bold", KoGenStyle::TextType); } else { // Remember : Qt and CSS/XSL doesn't have the same scale. Its 100-900 instead of Qts 0-100 style.addProperty("fo:font-weight", qBound(10, boldness, 90) * 10, KoGenStyle::TextType); } } } else if (key == QTextFormat::FontItalic) { if (d->stylesPrivate.value(key).toBool()) { style.addProperty("fo:font-style", "italic", KoGenStyle::TextType); } else { style.addProperty("fo:font-style", "normal", KoGenStyle::TextType); } } else if (key == QTextFormat::FontFamily) { QString fontFamily = d->stylesPrivate.value(key).toString(); style.addProperty("fo:font-family", fontFamily, KoGenStyle::TextType); } else if (key == QTextFormat::FontFixedPitch) { bool fixedPitch = d->stylesPrivate.value(key).toBool(); style.addProperty("style:font-pitch", fixedPitch ? "fixed" : "variable", KoGenStyle::TextType); // if this property is saved we also need to save the fo:font-family attribute as otherwise it will be ignored on loading as defined in the spec style.addProperty("fo:font-family", fontFamily(), KoGenStyle::TextType); } else if (key == QTextFormat::FontStyleHint) { bool ok = false; int styleHint = d->stylesPrivate.value(key).toInt(&ok); if (ok) { QString generic = exportOdfFontStyleHint((QFont::StyleHint) styleHint); if (!generic.isEmpty()) { style.addProperty("style:font-family-generic", generic, KoGenStyle::TextType); } // if this property is saved we also need to save the fo:font-family attribute as otherwise it will be ignored on loading as defined in the spec style.addProperty("fo:font-family", fontFamily(), KoGenStyle::TextType); } } else if (key == QTextFormat::FontKerning) { style.addProperty("style:letter-kerning", fontKerning() ? "true" : "false", KoGenStyle::TextType); } else if (key == QTextFormat::FontCapitalization) { switch (fontCapitalization()) { case QFont::SmallCaps: style.addProperty("fo:font-variant", "small-caps", KoGenStyle::TextType); break; case QFont::MixedCase: style.addProperty("fo:font-variant", "normal", KoGenStyle::TextType); style.addProperty("fo:text-transform", "none", KoGenStyle::TextType); break; case QFont::AllUppercase: style.addProperty("fo:text-transform", "uppercase", KoGenStyle::TextType); break; case QFont::AllLowercase: style.addProperty("fo:text-transform", "lowercase", KoGenStyle::TextType); break; case QFont::Capitalize: style.addProperty("fo:text-transform", "capitalize", KoGenStyle::TextType); break; } } else if (key == OverlineStyle) { bool ok = false; int styleId = d->stylesPrivate.value(key).toInt(&ok); if (ok) { style.addProperty("style:text-overline-style", exportOdfLineStyle((KoCharacterStyle::LineStyle) styleId), KoGenStyle::TextType); } } else if (key == OverlineType) { bool ok = false; int type = d->stylesPrivate.value(key).toInt(&ok); if (ok) { style.addProperty("style:text-overline-type", exportOdfLineType((KoCharacterStyle::LineType) type), KoGenStyle::TextType); } } else if (key == OverlineColor) { QColor color = d->stylesPrivate.value(key).value(); if (color.isValid()) style.addProperty("style:text-overline-color", color.name(), KoGenStyle::TextType); else style.addProperty("style:text-overline-color", "font-color", KoGenStyle::TextType); } else if (key == OverlineMode) { bool ok = false; int mode = d->stylesPrivate.value(key).toInt(&ok); if (ok) { style.addProperty("style:text-overline-mode", exportOdfLineMode((KoCharacterStyle::LineMode) mode), KoGenStyle::TextType); } } else if (key == OverlineWidth) { KoCharacterStyle::LineWeight weight; qreal width; overlineWidth(weight, width); style.addProperty("style:text-overline-width", exportOdfLineWidth(weight, width), KoGenStyle::TextType); } else if (key == UnderlineStyle) { bool ok = false; int styleId = d->stylesPrivate.value(key).toInt(&ok); if (ok) style.addProperty("style:text-underline-style", exportOdfLineStyle((KoCharacterStyle::LineStyle) styleId), KoGenStyle::TextType); } else if (key == UnderlineType) { bool ok = false; int type = d->stylesPrivate.value(key).toInt(&ok); if (ok) style.addProperty("style:text-underline-type", exportOdfLineType((KoCharacterStyle::LineType) type), KoGenStyle::TextType); } else if (key == QTextFormat::TextUnderlineColor) { QColor color = d->stylesPrivate.value(key).value(); if (color.isValid()) style.addProperty("style:text-underline-color", color.name(), KoGenStyle::TextType); else style.addProperty("style:text-underline-color", "font-color", KoGenStyle::TextType); } else if (key == UnderlineMode) { bool ok = false; int mode = d->stylesPrivate.value(key).toInt(&ok); if (ok) style.addProperty("style:text-underline-mode", exportOdfLineMode((KoCharacterStyle::LineMode) mode), KoGenStyle::TextType); } else if (key == UnderlineWidth) { KoCharacterStyle::LineWeight weight; qreal width; underlineWidth(weight, width); style.addProperty("style:text-underline-width", exportOdfLineWidth(weight, width), KoGenStyle::TextType); } else if (key == StrikeOutStyle) { bool ok = false; int styleId = d->stylesPrivate.value(key).toInt(&ok); if (ok) style.addProperty("style:text-line-through-style", exportOdfLineStyle((KoCharacterStyle::LineStyle) styleId), KoGenStyle::TextType); } else if (key == StrikeOutType) { bool ok = false; int type = d->stylesPrivate.value(key).toInt(&ok); if (ok) style.addProperty("style:text-line-through-type", exportOdfLineType((KoCharacterStyle::LineType) type), KoGenStyle::TextType); } else if (key == StrikeOutText) { style.addProperty("style:text-line-through-text", d->stylesPrivate.value(key).toString(), KoGenStyle::TextType); } else if (key == StrikeOutColor) { QColor color = d->stylesPrivate.value(key).value(); if (color.isValid()) style.addProperty("style:text-line-through-color", color.name(), KoGenStyle::TextType); } else if (key == StrikeOutMode) { bool ok = false; int mode = d->stylesPrivate.value(key).toInt(&ok); if (ok) style.addProperty("style:text-line-through-mode", exportOdfLineMode((KoCharacterStyle::LineMode) mode), KoGenStyle::TextType); } else if (key == StrikeOutWidth) { KoCharacterStyle::LineWeight weight; qreal width; strikeOutWidth(weight, width); style.addProperty("style:text-line-through-width", exportOdfLineWidth(weight, width), KoGenStyle::TextType); } else if (key == QTextFormat::BackgroundBrush) { QBrush brush = d->stylesPrivate.value(key).value(); if (brush.style() == Qt::NoBrush) style.addProperty("fo:background-color", "transparent", KoGenStyle::TextType); else style.addProperty("fo:background-color", brush.color().name(), KoGenStyle::TextType); } else if (key == QTextFormat::ForegroundBrush) { QBrush brush = d->stylesPrivate.value(key).value(); if (brush.style() != Qt::NoBrush) { style.addProperty("fo:color", brush.color().name(), KoGenStyle::TextType); } } else if (key == KoCharacterStyle::UseWindowFontColor) { bool use = d->stylesPrivate.value(key).toBool(); style.addProperty("style:use-window-font-color", use ? "true" : "false", KoGenStyle::TextType); } else if (key == QTextFormat::TextVerticalAlignment) { if (verticalAlignment() == QTextCharFormat::AlignSuperScript) style.addProperty("style:text-position", "super", KoGenStyle::TextType); else if (verticalAlignment() == QTextCharFormat::AlignSubScript) style.addProperty("style:text-position", "sub", KoGenStyle::TextType); else if (d->stylesPrivate.contains(QTextFormat::TextVerticalAlignment)) // no superscript or subscript style.addProperty("style:text-position", "0% 100%", KoGenStyle::TextType); } else if (key == QTextFormat::FontPointSize) { // when there is percentageFontSize!=100% property ignore the fontSize property and store the percentage property if ( (!hasProperty(KoCharacterStyle::PercentageFontSize)) || (percentageFontSize()==100)) style.addPropertyPt("fo:font-size", fontPointSize(), KoGenStyle::TextType); } else if (key == KoCharacterStyle::PercentageFontSize) { if(percentageFontSize()!=100) { style.addProperty("fo:font-size", QString::number(percentageFontSize()) + '%', KoGenStyle::TextType); } } else if (key == KoCharacterStyle::Country) { style.addProperty("fo:country", d->stylesPrivate.value(KoCharacterStyle::Country).toString(), KoGenStyle::TextType); } else if (key == KoCharacterStyle::Language) { style.addProperty("fo:language", d->stylesPrivate.value(KoCharacterStyle::Language).toString(), KoGenStyle::TextType); } else if (key == KoCharacterStyle::FontLetterSpacing) { qreal space = fontLetterSpacing(); style.addPropertyPt("fo:letter-spacing", space, KoGenStyle::TextType); } else if (key == QTextFormat::TextOutline) { QPen outline = textOutline(); style.addProperty("style:text-outline", outline.style() == Qt::NoPen ? "false" : "true", KoGenStyle::TextType); } else if (key == KoCharacterStyle::FontCharset) { style.addProperty("style:font-charset", d->stylesPrivate.value(KoCharacterStyle::FontCharset).toString(), KoGenStyle::TextType); // if this property is saved we also need to save the fo:font-family attribute as otherwise it will be ignored on loading as defined in the spec style.addProperty("fo:font-family", fontFamily(), KoGenStyle::TextType); } else if (key == KoCharacterStyle::TextRotationAngle) { style.addProperty("style:text-rotation-angle", QString::number(textRotationAngle()), KoGenStyle::TextType); } else if (key == KoCharacterStyle::TextRotationScale) { RotationScale scale = textRotationScale(); style.addProperty("style:text-rotation-scale", rotationScaleToString(scale), KoGenStyle::TextType); } else if (key == KoCharacterStyle::TextScale) { int scale = textScale(); style.addProperty("style:text-scale", QString::number(scale) + '%', KoGenStyle::TextType); } else if (key == KoCharacterStyle::TextShadow) { KoShadowStyle shadow = textShadow(); style.addProperty("fo:text-shadow", shadow.saveOdf(), KoGenStyle::TextType); } else if (key == KoCharacterStyle::TextCombine) { KoCharacterStyle::TextCombineType textCombineType = textCombine(); switch (textCombineType) { case KoCharacterStyle::NoTextCombine: style.addProperty("style:text-combine", "none", KoGenStyle::TextType); break; case KoCharacterStyle::TextCombineLetters: style.addProperty("style:text-combine", "letters", KoGenStyle::TextType); break; case KoCharacterStyle::TextCombineLines: style.addProperty("style:text-combine", "lines", KoGenStyle::TextType); break; } } else if (key == KoCharacterStyle::TextCombineEndChar) { style.addProperty("style:text-combine-end-char", textCombineEndChar(), KoGenStyle::TextType); } else if (key == KoCharacterStyle::TextCombineStartChar) { style.addProperty("style:text-combine-start-char", textCombineStartChar(), KoGenStyle::TextType); } else if (key == KoCharacterStyle::FontRelief) { KoCharacterStyle::ReliefType relief = fontRelief(); switch (relief) { case KoCharacterStyle::NoRelief: style.addProperty("style:font-relief", "none", KoGenStyle::TextType); break; case KoCharacterStyle::Embossed: style.addProperty("style:font-relief", "embossed", KoGenStyle::TextType); break; case KoCharacterStyle::Engraved: style.addProperty("style:font-relief", "engraved", KoGenStyle::TextType); break; } } else if (key == KoCharacterStyle::TextEmphasizeStyle) { KoCharacterStyle::EmphasisStyle emphasisStyle = textEmphasizeStyle(); KoCharacterStyle::EmphasisPosition position = textEmphasizePosition(); QString odfEmphasis; switch (emphasisStyle) { case KoCharacterStyle::NoEmphasis: odfEmphasis = "none"; break; case KoCharacterStyle::AccentEmphasis: odfEmphasis = "accent"; break; case KoCharacterStyle::CircleEmphasis: odfEmphasis = "circle"; break; case KoCharacterStyle::DiscEmphasis: odfEmphasis = "disc"; break; case KoCharacterStyle::DotEmphasis: odfEmphasis = "dot"; break; } if (hasProperty(KoCharacterStyle::TextEmphasizePosition)) { if (position == KoCharacterStyle::EmphasisAbove) odfEmphasis += " above"; else odfEmphasis += " below"; } style.addProperty("style:text-emphasize", odfEmphasis, KoGenStyle::TextType); } else if (key == KoCharacterStyle::HasHyphenation) { if (hasHyphenation()) style.addProperty("fo:hyphenate", "true", KoGenStyle::TextType); else style.addProperty("fo:hyphenate", "false", KoGenStyle::TextType); } else if (key == KoCharacterStyle::HyphenationPushCharCount) { style.addProperty("fo:hyphenation-push-char-count", hyphenationPushCharCount(), KoGenStyle::TextType); } else if (key == KoCharacterStyle::HyphenationRemainCharCount) { style.addProperty("fo:hyphenation-remain-char-count", hyphenationRemainCharCount(), KoGenStyle::TextType); } else if (key == KoCharacterStyle::Blink) { style.addProperty("style:text-blinking", blinking(), KoGenStyle::TextType); } } //TODO: font name and family } QVariant KoCharacterStyle::value(int key) const { QVariant variant = d->stylesPrivate.value(key); if (variant.isNull()) { if (d->parentStyle) variant = d->parentStyle->value(key); else if (d->defaultStyle) variant = d->defaultStyle->value(key); } return variant; } void KoCharacterStyle::removeHardCodedDefaults() { d->hardCodedDefaultStyle.clearAll(); } diff --git a/plugins/flake/textshape/textlayout/KoTextDocumentLayout.cpp b/plugins/flake/textshape/textlayout/KoTextDocumentLayout.cpp index 9c1381655b..465fa31162 100644 --- a/plugins/flake/textshape/textlayout/KoTextDocumentLayout.cpp +++ b/plugins/flake/textshape/textlayout/KoTextDocumentLayout.cpp @@ -1,1025 +1,1025 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007, 2009-2010 Thomas Zander * Copyright (C) 2010 Johannes Simon * Copyright (C) 2011-2013 KO GmbH * Copyright (C) 2011-2013 C.Boemann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoTextDocumentLayout.h" #include "styles/KoStyleManager.h" #include "KoTextBlockData.h" #include "KoInlineTextObjectManager.h" #include "KoTextLayoutRootArea.h" #include "KoTextLayoutRootAreaProvider.h" #include "KoTextLayoutObstruction.h" #include "FrameIterator.h" #include "InlineAnchorStrategy.h" #include "FloatingAnchorStrategy.h" #include "AnchorStrategy.h" #include "IndexGeneratorManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern int qt_defaultDpiY(); KoInlineObjectExtent::KoInlineObjectExtent(qreal ascent, qreal descent) : m_ascent(ascent), m_descent(descent) { } class Q_DECL_HIDDEN KoTextDocumentLayout::Private { public: Private(KoTextDocumentLayout *) : styleManager(0) , changeTracker(0) , inlineTextObjectManager(0) , textRangeManager(0) , provider(0) , layoutPosition(0) , anchoringRootArea(0) , anchoringIndex(0) , anAnchorIsPlaced(false) , anchoringSoftBreak(INT_MAX) , allowPositionInlineObject(true) , continuationObstruction(0) , referencedLayout(0) , defaultTabSizing(0) , y(0) , isLayouting(false) , layoutScheduled(false) , continuousLayout(true) , layoutBlocked(false) , changesBlocked(false) , restartLayout(false) , wordprocessingMode(false) , showInlineObjectVisualization(false) { } KoStyleManager *styleManager; KoChangeTracker *changeTracker; KoInlineTextObjectManager *inlineTextObjectManager; KoTextRangeManager *textRangeManager; KoTextLayoutRootAreaProvider *provider; KoPostscriptPaintDevice *paintDevice; QList rootAreaList; FrameIterator *layoutPosition; QHash inlineObjectExtents; // maps text-position to whole-line-height of an inline object int inlineObjectOffset; QList textAnchors; // list of all inserted inline objects QList foundAnchors; // anchors found in an iteration run KoTextLayoutRootArea *anchoringRootArea; int anchoringIndex; // index of last not positioned inline object inside textAnchors bool anAnchorIsPlaced; int anchoringSoftBreak; QRectF anchoringParagraphRect; QRectF anchoringParagraphContentRect; QRectF anchoringLayoutEnvironmentRect; bool allowPositionInlineObject; QHash anchoredObstructions; // all obstructions created because KoShapeAnchor from m_textAnchors is in text QList freeObstructions; // obstructions affecting the current rootArea, and not anchored to text KoTextLayoutObstruction *continuationObstruction; KoTextDocumentLayout *referencedLayout; QHash rootAreaForInlineObject; qreal defaultTabSizing; qreal y; bool isLayouting; bool layoutScheduled; bool continuousLayout; bool layoutBlocked; bool changesBlocked; bool restartLayout; bool wordprocessingMode; bool showInlineObjectVisualization; }; // ------------------- KoTextDocumentLayout -------------------- KoTextDocumentLayout::KoTextDocumentLayout(QTextDocument *doc, KoTextLayoutRootAreaProvider *provider) : QAbstractTextDocumentLayout(doc), d(new Private(this)) { d->paintDevice = new KoPostscriptPaintDevice(); d->provider = provider; setPaintDevice(d->paintDevice); d->styleManager = KoTextDocument(document()).styleManager(); d->changeTracker = KoTextDocument(document()).changeTracker(); d->inlineTextObjectManager = KoTextDocument(document()).inlineTextObjectManager(); d->textRangeManager = KoTextDocument(document()).textRangeManager(); setTabSpacing(MM_TO_POINT(23)); // use same default as open office d->layoutPosition = new FrameIterator(doc->rootFrame()); } KoTextDocumentLayout::~KoTextDocumentLayout() { delete d->paintDevice; delete d->layoutPosition; qDeleteAll(d->freeObstructions); qDeleteAll(d->anchoredObstructions); qDeleteAll(d->textAnchors); delete d; } KoTextLayoutRootAreaProvider *KoTextDocumentLayout::provider() const { return d->provider; } void KoTextDocumentLayout::setWordprocessingMode() { d->wordprocessingMode = true; } bool KoTextDocumentLayout::wordprocessingMode() const { return d->wordprocessingMode; } bool KoTextDocumentLayout::relativeTabs(const QTextBlock &block) const { return KoTextDocument(document()).relativeTabs() && KoTextDocument(block.document()).relativeTabs(); } KoInlineTextObjectManager *KoTextDocumentLayout::inlineTextObjectManager() const { return d->inlineTextObjectManager; } void KoTextDocumentLayout::setInlineTextObjectManager(KoInlineTextObjectManager *manager) { d->inlineTextObjectManager = manager; } KoTextRangeManager *KoTextDocumentLayout::textRangeManager() const { return d->textRangeManager; } void KoTextDocumentLayout::setTextRangeManager(KoTextRangeManager *manager) { d->textRangeManager = manager; } KoChangeTracker *KoTextDocumentLayout::changeTracker() const { return d->changeTracker; } void KoTextDocumentLayout::setChangeTracker(KoChangeTracker *tracker) { d->changeTracker = tracker; } KoStyleManager *KoTextDocumentLayout::styleManager() const { return d->styleManager; } void KoTextDocumentLayout::setStyleManager(KoStyleManager *manager) { d->styleManager = manager; } QRectF KoTextDocumentLayout::blockBoundingRect(const QTextBlock &block) const { QTextLayout *layout = block.layout(); return layout->boundingRect(); } QSizeF KoTextDocumentLayout::documentSize() const { return QSizeF(); } QRectF KoTextDocumentLayout::selectionBoundingBox(QTextCursor &cursor) const { QRectF retval; Q_FOREACH (const KoTextLayoutRootArea *rootArea, d->rootAreaList) { if (!rootArea->isDirty()) { QRectF areaBB = rootArea->selectionBoundingBox(cursor); if (areaBB.isValid()) { retval |= areaBB; } } } return retval; } void KoTextDocumentLayout::draw(QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context) { // WARNING Text shapes ask their root area directly to paint. // It saves a lot of extra traversal, that is quite costly for big // documents Q_UNUSED(painter); Q_UNUSED(context); } int KoTextDocumentLayout::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const { Q_UNUSED(point); Q_UNUSED(accuracy); Q_ASSERT(false); //we should no longer call this method. // There is no need and is just slower than needed // call rootArea->hitTest() directly // root area is available through KoTextShapeData return -1; } int KoTextDocumentLayout::pageCount() const { return 1; } void KoTextDocumentLayout::setTabSpacing(qreal spacing) { d->defaultTabSizing = spacing; } qreal KoTextDocumentLayout::defaultTabSpacing() const { return d->defaultTabSizing; } // this method is called on every char inserted or deleted, on format changes, setting/moving of variables or objects. void KoTextDocumentLayout::documentChanged(int position, int charsRemoved, int charsAdded) { if (d->changesBlocked) { return; } int from = position; const int to = from + charsAdded; while (from < to) { // find blocks that have been added QTextBlock block = document()->findBlock(from); if (! block.isValid()) break; if (from == block.position() && block.textList()) { KoTextBlockData data(block); data.setCounterWidth(-1); } from = block.position() + block.length(); } // Mark the to the position corresponding root-areas as dirty. If there is no root-area for the position then we // don't need to mark anything dirty but still need to go on to force a scheduled relayout. if (!d->rootAreaList.isEmpty()) { KoTextLayoutRootArea *fromArea; if (position) { fromArea = rootAreaForPosition(position-1); } else { fromArea = d->rootAreaList.at(0); } int startIndex = fromArea ? qMax(0, d->rootAreaList.indexOf(fromArea)) : 0; int endIndex = startIndex; if (charsRemoved != 0 || charsAdded != 0) { // If any characters got removed or added make sure to also catch other root-areas that may be // affected by this change. Note that adding, removing or formatting text will always charsRemoved>0 // and charsAdded>0 cause they are changing a range of characters. One case where both is zero is if // the content of a variable changed (see KoVariable::setValue which calls publicDocumentChanged). In // those cases we only need to relayout the root-area dirty where the variable is on. KoTextLayoutRootArea *toArea = fromArea ? rootAreaForPosition(position + qMax(charsRemoved, charsAdded) + 1) : 0; if (toArea) { if (toArea != fromArea) { endIndex = qMax(startIndex, d->rootAreaList.indexOf(toArea)); } else { endIndex = startIndex; } } else { endIndex = d->rootAreaList.count() - 1; } // The previous and following root-area of that range are selected too cause they can also be affect by // changes done to the range of root-areas. if (startIndex >= 1) --startIndex; if (endIndex + 1 < d->rootAreaList.count()) ++endIndex; } // Mark all selected root-areas as dirty so they are relayouted. for(int i = startIndex; i <= endIndex; ++i) { if (d->rootAreaList.size() > i && d->rootAreaList[i]) d->rootAreaList[i]->setDirty(); } } // Once done we emit the layoutIsDirty signal. The consumer (e.g. the TextShape) will then layout dirty // root-areas and if needed following ones which got dirty cause content moved to them. Also this will // created new root-areas using KoTextLayoutRootAreaProvider::provide if needed. emitLayoutIsDirty(); } KoTextLayoutRootArea *KoTextDocumentLayout::rootAreaForPosition(int position) const { QTextBlock block = document()->findBlock(position); if (!block.isValid()) return 0; QTextLine line = block.layout()->lineForTextPosition(position - block.position()); if (!line.isValid()) return 0; foreach (KoTextLayoutRootArea *rootArea, d->rootAreaList) { QRectF rect = rootArea->boundingRect(); // should already be normalized() if (rect.width() <= 0.0 && rect.height() <= 0.0) // ignore the rootArea if it has a size of QSizeF(0,0) continue; QPointF pos = line.position(); qreal x = pos.x(); qreal y = pos.y(); //0.125 needed since Qt Scribe works with fixed point if (x + 0.125 >= rect.x() && x<= rect.right() && y + line.height() + 0.125 >= rect.y() && y <= rect.bottom()) { return rootArea; } } return 0; } KoTextLayoutRootArea *KoTextDocumentLayout::rootAreaForPoint(const QPointF &point) const { Q_FOREACH (KoTextLayoutRootArea *rootArea, d->rootAreaList) { if (!rootArea->isDirty()) { if (rootArea->boundingRect().contains(point)) { return rootArea; } } } return 0; } void KoTextDocumentLayout::showInlineObjectVisualization(bool show) { d->showInlineObjectVisualization = show; } void KoTextDocumentLayout::drawInlineObject(QPainter *painter, const QRectF &rect, QTextInlineObject object, int position, const QTextFormat &format) { Q_ASSERT(format.isCharFormat()); if (d->inlineTextObjectManager == 0) return; QTextCharFormat cf = format.toCharFormat(); if (d->showInlineObjectVisualization) { QColor color = cf.foreground().color(); // initial idea was to use Qt::gray (#A0A0A4) // for non-black text on non-white background it was derived to use // the text color with a transparency of 0x5F, so white-gray (0xFF-0xA0) color.setAlpha(0x5F); cf.setBackground(QBrush(color)); } KoInlineObject *obj = d->inlineTextObjectManager->inlineTextObject(cf); if (obj) obj->paint(*painter, paintDevice(), document(), rect, object, position, cf); } QList KoTextDocumentLayout::textAnchors() const { return d->textAnchors; } void KoTextDocumentLayout::registerAnchoredObstruction(KoTextLayoutObstruction *obstruction) { d->anchoredObstructions.insert(obstruction->shape(), obstruction); } qreal KoTextDocumentLayout::maxYOfAnchoredObstructions(int firstCursorPosition, int lastCursorPosition) const { qreal y = 0.0; int index = 0; while (index < d->anchoringIndex) { Q_ASSERT(index < d->textAnchors.count()); KoShapeAnchor *anchor = d->textAnchors[index]; if (anchor->flowWithText()) { if (anchor->textLocation()->position() >= firstCursorPosition && anchor->textLocation()->position() <= lastCursorPosition) { y = qMax(y, anchor->shape()->boundingRect().bottom() - anchor->shape()->parent()->boundingRect().y()); } } ++index; } return y; } int KoTextDocumentLayout::anchoringSoftBreak() const { return d->anchoringSoftBreak; } void KoTextDocumentLayout::positionAnchoredObstructions() { if (!d->anchoringRootArea) return; KoTextPage *page = d->anchoringRootArea->page(); if (!page) return; if (d->anAnchorIsPlaced) return; // The specs define 3 different anchor modes using the // draw:wrap-influence-on-position. We only implement the // once-successive and decided against supporting the other // two modes cause; // 1. The first mode, once-concurrently, is only for backward-compatibility // with pre OpenOffice.org 1.1. No other application supports that. It // should never have been added to the specs. // 2. The iterative mode is undocumented and it's absolute unclear how to // implement it in a way that we would earn 100% the same results OO.org // produces. In fact by looking at the OO.org source-code there seem to // be lot of extra-conditions, assumptions and OO.org related things going // on to handle that mode. We tried to support that mode once and it did // hit us bad, our source-code become way more worse, layouting slower and // the result was still different from OO.org. So, we decided it's not // worth it. - // 3. The explanation provided at http://lists.oasis-open.org/archives/office/200409/msg00018.html + // 3. The explanation provided at https://lists.oasis-open.org/archives/office/200409/msg00018.html // why the specs support those 3 anchor modes is, well, poor. It just doesn't // make sense. The specs should be fixed. // 4. The only support mode, the once-successive, is the one (only) support by // MSOffice. It's clear, logical, easy and needs to be supported by all // major office-suites that like to be compatible with MSOffice and OO.org. if (d->anchoringIndex < d->textAnchors.count()) { KoShapeAnchor *textAnchor = d->textAnchors[d->anchoringIndex]; AnchorStrategy *strategy = static_cast(textAnchor->placementStrategy()); strategy->setPageRect(page->rect()); strategy->setPageContentRect(page->contentRect()); strategy->setPageNumber(page->pageNumber()); if (strategy->moveSubject()) { ++d->anchoringIndex; d->anAnchorIsPlaced = true; } } } void KoTextDocumentLayout::setAnchoringParagraphRect(const QRectF ¶graphRect) { d->anchoringParagraphRect = paragraphRect; } void KoTextDocumentLayout::setAnchoringParagraphContentRect(const QRectF ¶graphContentRect) { d->anchoringParagraphContentRect = paragraphContentRect; } void KoTextDocumentLayout::setAnchoringLayoutEnvironmentRect(const QRectF &layoutEnvironmentRect) { d->anchoringLayoutEnvironmentRect = layoutEnvironmentRect; } void KoTextDocumentLayout::allowPositionInlineObject(bool allow) { d->allowPositionInlineObject = allow; } // This method is called by qt every time QTextLine.setWidth()/setNumColumns() is called void KoTextDocumentLayout::positionInlineObject(QTextInlineObject item, int position, const QTextFormat &format) { // Note: "item" used to be what was positioned. We don't actually use qtextinlineobjects anymore // for our inline objects, but get the id from the format. Q_UNUSED(item); //We are called before layout so that we can position objects Q_ASSERT(format.isCharFormat()); if (d->inlineTextObjectManager == 0) return; if (!d->allowPositionInlineObject) return; QTextCharFormat cf = format.toCharFormat(); KoInlineObject *obj = d->inlineTextObjectManager->inlineTextObject(cf); // We need some special treatment for anchors as they need to position their object during // layout and not this early KoAnchorInlineObject *anchorObject = dynamic_cast(obj); if (anchorObject && d->anchoringRootArea->associatedShape()) { // The type can only be KoShapeAnchor::AnchorAsCharacter since it's inline KoShapeAnchor *anchor = anchorObject->anchor(); d->foundAnchors.append(anchor); // if there is no anchor strategy set then create one if (!anchor->placementStrategy()) { anchor->setPlacementStrategy(new InlineAnchorStrategy(anchorObject, d->anchoringRootArea)); d->textAnchors.append(anchor); anchorObject->updatePosition(document(), position, cf); // by extension calls updateContainerModel } static_cast(anchor->placementStrategy())->setParagraphRect(d->anchoringParagraphRect); static_cast(anchor->placementStrategy())->setParagraphContentRect(d->anchoringParagraphContentRect); static_cast(anchor->placementStrategy())->setLayoutEnvironmentRect(d->anchoringLayoutEnvironmentRect); } else if (obj) { obj->updatePosition(document(), position, cf); } } // This method is called by KoTextLauoutArea every time it encounters a KoAnchorTextRange void KoTextDocumentLayout::positionAnchorTextRanges(int pos, int length, const QTextDocument *effectiveDocument) { if (!d->allowPositionInlineObject) return; if (!textRangeManager()) { return; } QHash ranges = textRangeManager()->textRangesChangingWithin(effectiveDocument, pos, pos+length, pos, pos+length); Q_FOREACH (KoTextRange *range, ranges.values()) { KoAnchorTextRange *anchorRange = dynamic_cast(range); if (anchorRange) { // We need some special treatment for anchors as they need to position their object during // layout and not this early KoShapeAnchor *anchor = anchorRange->anchor(); d->foundAnchors.append(anchor); // At the beginAnchorCollecting the strategy is cleared, so this if will be entered // every time we layout a page (though not every time for the inner repeats due to anchors) if (!anchor->placementStrategy()) { int index = d->textAnchors.count(); anchor->setPlacementStrategy(new FloatingAnchorStrategy(anchorRange, d->anchoringRootArea)); // The purpose of following code-block is to be sure that our paragraph-anchors are // properly sorted by their z-index so the FloatingAnchorStrategy::checkStacking // logic stack in the proper order. Bug 274512 has a testdoc for this attached. if (index > 0 && anchor->anchorType() == KoShapeAnchor::AnchorParagraph && (anchor->horizontalRel() == KoShapeAnchor::HParagraph || anchor->horizontalRel() == KoShapeAnchor::HParagraphContent) && (anchor->horizontalPos() == KoShapeAnchor::HLeft || anchor->horizontalPos() == KoShapeAnchor::HRight)) { QTextBlock anchorBlock = document()->findBlock(anchorRange->position()); for(int i = index - 1; i >= 0; --i) { KoShapeAnchor *a = d->textAnchors[i]; if (a->anchorType() != anchor->anchorType()) break; if (a->horizontalPos() != anchor->horizontalPos()) break; if (document()->findBlock(a->textLocation()->position()) != anchorBlock) break; if (a->shape()->zIndex() < anchor->shape()->zIndex()) break; --index; } } d->textAnchors.insert(index, anchor); anchorRange->updateContainerModel(); } static_cast(anchor->placementStrategy())->setParagraphRect(d->anchoringParagraphRect); static_cast(anchor->placementStrategy())->setParagraphContentRect(d->anchoringParagraphContentRect); static_cast(anchor->placementStrategy())->setLayoutEnvironmentRect(d->anchoringLayoutEnvironmentRect); } KoAnnotation *annotation = dynamic_cast(range); if (annotation) { int position = range->rangeStart(); QTextBlock block = range->document()->findBlock(position); QTextLine line = block.layout()->lineForTextPosition(position - block.position()); QPointF refPos(line.cursorToX(position - block.position()), line.y()); KoShape *refShape = d->anchoringRootArea->associatedShape(); //KoTextShapeData *refTextShapeData; //refPos += QPointF(refTextShapeData->leftPadding(), -refTextShapeData->documentOffset() + refTextShapeData->topPadding()); refPos += QPointF(0, -d->anchoringRootArea->top()); refPos = refShape->absoluteTransformation().map(refPos); //FIXME we need a more precise position than anchorParagraph Rect emit foundAnnotation(annotation->annotationShape(), refPos); } } } void KoTextDocumentLayout::beginAnchorCollecting(KoTextLayoutRootArea *rootArea) { for(int i = 0; itextAnchors.size(); i++ ) { d->textAnchors[i]->setPlacementStrategy(0); } qDeleteAll(d->anchoredObstructions); d->anchoredObstructions.clear(); d->textAnchors.clear(); d->anchoringIndex = 0; d->anAnchorIsPlaced = false; d->anchoringRootArea = rootArea; d->allowPositionInlineObject = true; d->anchoringSoftBreak = INT_MAX; } void KoTextDocumentLayout::resizeInlineObject(QTextInlineObject item, int position, const QTextFormat &format) { // Note: This method is called by qt during layout AND during paint Q_ASSERT(format.isCharFormat()); if (d->inlineTextObjectManager == 0) return; QTextCharFormat cf = format.toCharFormat(); KoInlineObject *obj = d->inlineTextObjectManager->inlineTextObject(cf); if (!obj) { return; } if (d->isLayouting) { d->rootAreaForInlineObject[obj] = d->anchoringRootArea; } KoTextLayoutRootArea *rootArea = d->rootAreaForInlineObject.value(obj); if (rootArea == 0 || rootArea->associatedShape() == 0) return; QTextDocument *doc = document(); QVariant v; v.setValue(rootArea->page()); doc->addResource(KoTextDocument::LayoutTextPage, KoTextDocument::LayoutTextPageUrl, v); obj->resize(doc, item, position, cf, paintDevice()); registerInlineObject(item); } void KoTextDocumentLayout::emitLayoutIsDirty() { emit layoutIsDirty(); } void KoTextDocumentLayout::layout() { if (d->layoutBlocked) { return; } if (IndexGeneratorManager::instance(document())->generate()) { return; } Q_ASSERT(!d->isLayouting); d->isLayouting = true; bool finished; do { // Try to layout as long as d->restartLayout==true. This can happen for example if // a schedule layout call interrupts the layouting and asks for a new layout run. finished = doLayout(); } while (d->restartLayout); Q_ASSERT(d->isLayouting); d->isLayouting = false; if (finished) { // We are only finished with layouting if continuousLayout()==true. emit finishedLayout(); } } RootAreaConstraint constraintsForPosition(QTextFrame::iterator it, bool previousIsValid) { RootAreaConstraint constraints; constraints.masterPageName.clear(); constraints.visiblePageNumber = -1; constraints.newPageForced = false; QTextBlock block = it.currentBlock(); QTextTable *table = qobject_cast(it.currentFrame()); if (block.isValid()) { constraints.masterPageName = block.blockFormat().stringProperty(KoParagraphStyle::MasterPageName); if (block.blockFormat().hasProperty(KoParagraphStyle::PageNumber)) { constraints.visiblePageNumber = block.blockFormat().intProperty(KoParagraphStyle::PageNumber); } constraints.newPageForced = block.blockFormat().intProperty(KoParagraphStyle::BreakBefore) == KoText::PageBreak; } if (table) { constraints.masterPageName = table->frameFormat().stringProperty(KoTableStyle::MasterPageName); if (table->frameFormat().hasProperty(KoTableStyle::PageNumber)) { constraints.visiblePageNumber = table->frameFormat().intProperty(KoTableStyle::PageNumber); } constraints.newPageForced = table->frameFormat().intProperty(KoTableStyle::BreakBefore) == KoText::PageBreak; } if (!constraints.masterPageName.isEmpty()) { constraints.newPageForced = true; } if (previousIsValid && !constraints.newPageForced) { it--; block = it.currentBlock(); table = qobject_cast(it.currentFrame()); if (block.isValid()) { constraints.newPageForced = block.blockFormat().intProperty(KoParagraphStyle::BreakAfter) == KoText::PageBreak; } if (table) { constraints.newPageForced = table->frameFormat().intProperty(KoTableStyle::BreakAfter) == KoText::PageBreak; } } return constraints; } bool KoTextDocumentLayout::doLayout() { delete d->layoutPosition; d->layoutPosition = new FrameIterator(document()->rootFrame()); d->y = 0; d->layoutScheduled = false; d->restartLayout = false; FrameIterator *transferedFootNoteCursor = 0; KoInlineNote *transferedContinuedNote = 0; int footNoteAutoCount = 0; KoTextLayoutRootArea *rootArea = 0; d->rootAreaList.clear(); int currentAreaNumber = 0; do { if (d->restartLayout) { return false; // Abort layouting to restart from the beginning. } // Build our request for our rootArea provider RootAreaConstraint constraints = constraintsForPosition(d->layoutPosition->it, currentAreaNumber > 0); // Request a new root-area. If 0 is returned then layouting is finished. bool newRootArea = false; rootArea = d->provider->provide(this, constraints, currentAreaNumber, &newRootArea); if (!rootArea) { // Out of space ? Nothing more to do break; } d->rootAreaList.append(rootArea); bool shouldLayout = false; if (rootArea->top() != d->y) { shouldLayout = true; } else if (rootArea->isDirty()) { shouldLayout = true; } else if (!rootArea->isStartingAt(d->layoutPosition)) { shouldLayout = true; } else if (newRootArea) { shouldLayout = true; } if (shouldLayout) { QRectF rect = d->provider->suggestRect(rootArea); d->freeObstructions = d->provider->relevantObstructions(rootArea); rootArea->setReferenceRect(rect.left(), rect.right(), d->y + rect.top(), d->y + rect.bottom()); beginAnchorCollecting(rootArea); // Layout all that can fit into that root area bool finished; FrameIterator *tmpPosition = 0; do { rootArea->setFootNoteCountInDoc(footNoteAutoCount); rootArea->setFootNoteFromPrevious(transferedFootNoteCursor, transferedContinuedNote); d->foundAnchors.clear(); delete tmpPosition; tmpPosition = new FrameIterator(d->layoutPosition); finished = rootArea->layoutRoot(tmpPosition); if (d->anAnchorIsPlaced) { d->anAnchorIsPlaced = false; } else { ++d->anchoringIndex; } } while (d->anchoringIndex < d->textAnchors.count()); foreach (KoShapeAnchor *anchor, d->textAnchors) { if (!d->foundAnchors.contains(anchor)) { d->anchoredObstructions.remove(anchor->shape()); d->anchoringSoftBreak = qMin(d->anchoringSoftBreak, anchor->textLocation()->position()); } } if (d->textAnchors.count() > 0) { delete tmpPosition; tmpPosition = new FrameIterator(d->layoutPosition); finished = rootArea->layoutRoot(tmpPosition); } delete d->layoutPosition; d->layoutPosition = tmpPosition; d->provider->doPostLayout(rootArea, newRootArea); updateProgress(d->layoutPosition->it); if (finished && !rootArea->footNoteCursorToNext()) { d->provider->releaseAllAfter(rootArea); // We must also delete them from our own list too int newsize = d->rootAreaList.indexOf(rootArea) + 1; while (d->rootAreaList.size() > newsize) { d->rootAreaList.removeLast(); } return true; // Finished layouting } if (d->layoutPosition->it == document()->rootFrame()->end()) { return true; // Finished layouting } if (!continuousLayout()) { return false; // Let's take a break. We are not finished layouting yet. } } else { // Drop following rootAreas delete d->layoutPosition; d->layoutPosition = new FrameIterator(rootArea->nextStartOfArea()); if (d->layoutPosition->it == document()->rootFrame()->end() && !rootArea->footNoteCursorToNext()) { d->provider->releaseAllAfter(rootArea); // We must also delete them from our own list too int newsize = d->rootAreaList.indexOf(rootArea) + 1; while (d->rootAreaList.size() > newsize) { d->rootAreaList.removeLast(); } return true; // Finished layouting } } transferedFootNoteCursor = rootArea->footNoteCursorToNext(); transferedContinuedNote = rootArea->continuedNoteToNext(); footNoteAutoCount += rootArea->footNoteAutoCount(); d->y = rootArea->bottom() + qreal(50); // (post)Layout method(s) just set this // 50 just to separate pages currentAreaNumber++; } while (transferedFootNoteCursor || d->layoutPosition->it != document()->rootFrame()->end()); return true; // Finished layouting } void KoTextDocumentLayout::scheduleLayout() { // Compress multiple scheduleLayout calls into one executeScheduledLayout. if (d->layoutScheduled) { return; } d->layoutScheduled = true; QTimer::singleShot(0, this, SLOT(executeScheduledLayout())); } void KoTextDocumentLayout::executeScheduledLayout() { // Only do the actual layout if it wasn't done meanwhile by someone else. if (!d->layoutScheduled) { return; } d->layoutScheduled = false; if (d->isLayouting) { // Since we are already layouting ask for a restart to be sure to also include // root-areas that got dirty and are before the currently processed root-area. d->restartLayout = true; } else { layout(); } } bool KoTextDocumentLayout::continuousLayout() const { return d->continuousLayout; } void KoTextDocumentLayout::setContinuousLayout(bool continuous) { d->continuousLayout = continuous; } void KoTextDocumentLayout::setBlockLayout(bool block) { d->layoutBlocked = block; } bool KoTextDocumentLayout::layoutBlocked() const { return d->layoutBlocked; } void KoTextDocumentLayout::setBlockChanges(bool block) { d->changesBlocked = block; } bool KoTextDocumentLayout::changesBlocked() const { return d->changesBlocked; } KoTextDocumentLayout* KoTextDocumentLayout::referencedLayout() const { return d->referencedLayout; } void KoTextDocumentLayout::setReferencedLayout(KoTextDocumentLayout *layout) { d->referencedLayout = layout; } QRectF KoTextDocumentLayout::frameBoundingRect(QTextFrame*) const { return QRectF(); } void KoTextDocumentLayout::clearInlineObjectRegistry(const QTextBlock &block) { d->inlineObjectExtents.clear(); d->inlineObjectOffset = block.position(); } void KoTextDocumentLayout::registerInlineObject(const QTextInlineObject &inlineObject) { KoInlineObjectExtent pos(inlineObject.ascent(),inlineObject.descent()); d->inlineObjectExtents.insert(d->inlineObjectOffset + inlineObject.textPosition(), pos); } KoInlineObjectExtent KoTextDocumentLayout::inlineObjectExtent(const QTextFragment &fragment) { if (d->inlineObjectExtents.contains(fragment.position())) return d->inlineObjectExtents[fragment.position()]; return KoInlineObjectExtent(); } void KoTextDocumentLayout::setContinuationObstruction(KoTextLayoutObstruction *continuationObstruction) { if (d->continuationObstruction) { delete d->continuationObstruction; } d->continuationObstruction = continuationObstruction; } QList KoTextDocumentLayout::currentObstructions() { if (d->continuationObstruction) { // () is needed so we append to a local list and not anchoredObstructions return (d->freeObstructions + d->anchoredObstructions.values()) << d->continuationObstruction; } else { return d->freeObstructions + d->anchoredObstructions.values(); } } QList KoTextDocumentLayout::rootAreas() const { return d->rootAreaList; } void KoTextDocumentLayout::removeRootArea(KoTextLayoutRootArea *rootArea) { int indexOf = rootArea ? qMax(0, d->rootAreaList.indexOf(rootArea)) : 0; for(int i = d->rootAreaList.count() - 1; i >= indexOf; --i) d->rootAreaList.removeAt(i); } QList KoTextDocumentLayout::shapes() const { QList listOfShapes; foreach (KoTextLayoutRootArea *rootArea, d->rootAreaList) { if (rootArea->associatedShape()) listOfShapes.append(rootArea->associatedShape()); } return listOfShapes; } void KoTextDocumentLayout::updateProgress(const QTextFrame::iterator &it) { QTextBlock block = it.currentBlock(); if (block.isValid()) { int percent = block.position() / qreal(document()->rootFrame()->lastPosition()) * 100.0; emit layoutProgressChanged(percent); } else if (it.currentFrame()) { int percent = it.currentFrame()->firstPosition() / qreal(document()->rootFrame()->lastPosition()) * 100.0; emit layoutProgressChanged(percent); } } diff --git a/plugins/flake/textshape/textlayout/KoTextLayoutArea_paint.cpp b/plugins/flake/textshape/textlayout/KoTextLayoutArea_paint.cpp index ff90af68da..e569fc12da 100644 --- a/plugins/flake/textshape/textlayout/KoTextLayoutArea_paint.cpp +++ b/plugins/flake/textshape/textlayout/KoTextLayoutArea_paint.cpp @@ -1,1200 +1,1200 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2008 Roopesh Chander * Copyright (C) 2007-2008 Pierre Ducroquet * Copyright (C) 2009-2011 KO GmbH * Copyright (C) 2009-2012 C. Boemann * Copyright (C) 2010 Nandita Suri * Copyright (C) 2010 Ajay Pundhir * Copyright (C) 2011 Lukáš Tvrdý * Copyright (C) 2011 Gopalakrishna Bhat A * Copyright (C) 2011 Stuart Dickson * Copyright (C) 2014 Denis Kuplyakov * * 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 "KoTextLayoutArea.h" #include "KoTextLayoutEndNotesArea.h" #include "KoTextLayoutTableArea.h" #include "KoTextLayoutNoteArea.h" #include "TableIterator.h" #include "ListItemsHelper.h" #include "RunAroundHelper.h" #include "KoTextDocumentLayout.h" #include "FrameIterator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_painting_tweaks.h" extern int qt_defaultDpiY(); Q_DECLARE_METATYPE(QTextDocument *) #define DropCapsAdditionalFormattingId 25602902 #include "KoTextLayoutArea_p.h" void KoTextLayoutArea::paint(QPainter *painter, const KoTextDocumentLayout::PaintContext &context) { if (d->startOfArea == 0 || d->endOfArea == 0) // We have not been layouted yet return; /* struct Timer { QTime d->time; Timer() { d->time.start(); } ~Timer() { warnTextLayout << "elapsed=" << d->time.elapsed(); } }; Timer timer; */ painter->save(); painter->translate(0, d->verticalAlignOffset); painter->setPen(context.textContext.palette.color(QPalette::Text)); // for text that has no color. const QRegion clipRegion = KisPaintingTweaks::safeClipRegion(*painter); // fetch after painter->translate so the clipRegion is correct KoTextBlockBorderData *lastBorder = 0; QRectF lastBorderRect; QTextFrame::iterator it = d->startOfArea->it; QTextFrame::iterator stop = d->endOfArea->it; if (!stop.atEnd()) { if (!stop.currentBlock().isValid() || d->endOfArea->lineTextStart >= 0) { // Last thing we show is a frame (table) or first part of a paragraph split in two // The stop point should be the object after that // However if stop is already atEnd we shouldn't increment further ++stop; } } int tableAreaIndex = 0; int blockIndex = 0; int tocIndex = 0; for (; it != stop && !it.atEnd(); ++it) { QTextBlock block = it.currentBlock(); QTextTable *table = qobject_cast(it.currentFrame()); QTextFrame *subFrame = it.currentFrame(); QTextBlockFormat format = block.blockFormat(); if (!block.isValid()) { if (lastBorder) { // draw previous block's border lastBorder->paint(*painter, lastBorderRect); lastBorder = 0; } } if (table) { if (tableAreaIndex >= d->tableAreas.size()) { continue; } d->tableAreas[tableAreaIndex]->paint(painter, context); ++tableAreaIndex; continue; } else if (subFrame) { if (subFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { d->endNotesArea->paint(painter, context); } continue; } else { if (!block.isValid()) { continue; } } if (block.blockFormat().hasProperty(KoParagraphStyle::GeneratedDocument)) { // Possibly paint the selection of the entire Table of Contents // but since it's a secondary document we need to create a fake selection QVariant data = block.blockFormat().property(KoParagraphStyle::GeneratedDocument); QTextDocument *generatedDocument = data.value(); KoTextDocumentLayout::PaintContext tocContext = context; tocContext.textContext.selections = QVector(); bool pure = true; Q_FOREACH (const QAbstractTextDocumentLayout::Selection &selection, context.textContext.selections) { if (selection.cursor.selectionStart() <= block.position() && selection.cursor.selectionEnd() >= block.position()) { painter->fillRect(d->generatedDocAreas[tocIndex]->boundingRect(), selection.format.background()); if (pure) { tocContext.textContext.selections.append(QAbstractTextDocumentLayout::Selection()); tocContext.textContext.selections[0].cursor = QTextCursor(generatedDocument); tocContext.textContext.selections[0].cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); tocContext.textContext.selections[0].format = selection.format; pure = false; } } } d->generatedDocAreas[tocIndex]->paint(painter, tocContext); ++tocIndex; continue; } QTextLayout *layout = block.layout(); KoTextBlockBorderData *border = 0; if (blockIndex >= d->blockRects.count()) break; QRectF br = d->blockRects[blockIndex]; ++blockIndex; if (!painter->hasClipping() || clipRegion.intersects(br.toRect())) { KoTextBlockData blockData(block); border = blockData.border(); KoTextBlockPaintStrategyBase *paintStrategy = blockData.paintStrategy(); KoTextBlockPaintStrategyBase dummyPaintStrategy; if (paintStrategy == 0) { paintStrategy = &dummyPaintStrategy; } if (!paintStrategy->isVisible()) { if (lastBorder) { // draw previous block's border lastBorder->paint(*painter, lastBorderRect); lastBorder = 0; } continue; // this paragraph shouldn't be shown so just skip it } // Check and update border drawing code if (lastBorder == 0) { lastBorderRect = br; } else if (lastBorder != border || lastBorderRect.width() != br.width() || lastBorderRect.x() != br.x()) { lastBorder->paint(*painter, lastBorderRect); lastBorderRect = br; } else { lastBorderRect = lastBorderRect.united(br); } lastBorder = border; painter->save(); QBrush bg = paintStrategy->background(block.blockFormat().background()); if (bg != Qt::NoBrush ) { painter->fillRect(br, bg); } else { bg = context.background; } paintStrategy->applyStrategy(painter); painter->save(); drawListItem(painter, block); painter->restore(); QVector selections; if (context.showSelections) { Q_FOREACH (const QAbstractTextDocumentLayout::Selection & selection, context.textContext.selections) { QTextCursor cursor = selection.cursor; int begin = cursor.position(); int end = cursor.anchor(); if (begin > end) std::swap(begin, end); if (end < block.position() || begin > block.position() + block.length()) continue; // selection does not intersect this block. if (selection.cursor.hasComplexSelection()) { continue; // selections of several table cells are covered by the within drawBorders above. } if (d->documentLayout->changeTracker() && !d->documentLayout->changeTracker()->displayChanges() && d->documentLayout->changeTracker()->containsInlineChanges(selection.format) && d->documentLayout->changeTracker()->elementById(selection.format.property(KoCharacterStyle::ChangeTrackerId).toInt())->isEnabled() && d->documentLayout->changeTracker()->elementById(selection.format.property(KoCharacterStyle::ChangeTrackerId).toInt())->getChangeType() == KoGenChange::DeleteChange) { continue; // Deletions should not be shown. } QTextLayout::FormatRange fr; fr.start = begin - block.position(); fr.length = end - begin; fr.format = selection.format; selections.append(fr); } } // this is a workaround to fix text getting cut of when format ranges are used. There // is a bug in Qt that can hit when text lines overlap each other. In case a format range // is used for formatting it can clip the lines above/below as Qt creates a clip rect for // the places it already painted for the format range which results in clippling. So use // the format range always to paint the text. QVector workaroundFormatRanges; for (QTextBlock::iterator it = block.begin(); !(it.atEnd()); ++it) { QTextFragment currentFragment = it.fragment(); if (currentFragment.isValid()) { bool formatChanged = false; QTextCharFormat format = currentFragment.charFormat(); int changeId = format.intProperty(KoCharacterStyle::ChangeTrackerId); if (changeId && d->documentLayout->changeTracker() && d->documentLayout->changeTracker()->displayChanges()) { KoChangeTrackerElement *changeElement = d->documentLayout->changeTracker()->elementById(changeId); switch(changeElement->getChangeType()) { case (KoGenChange::InsertChange): format.setBackground(QBrush(d->documentLayout->changeTracker()->getInsertionBgColor())); break; case (KoGenChange::FormatChange): format.setBackground(QBrush(d->documentLayout->changeTracker()->getFormatChangeBgColor())); break; case (KoGenChange::DeleteChange): format.setBackground(QBrush(d->documentLayout->changeTracker()->getDeletionBgColor())); break; case (KoGenChange::UNKNOWN): break; } formatChanged = true; } if (format.isAnchor()) { if (!format.hasProperty(KoCharacterStyle::UnderlineStyle)) format.setFontUnderline(true); if (!format.hasProperty(QTextFormat::ForegroundBrush)) format.setForeground(Qt::blue); formatChanged = true; } if (format.boolProperty(KoCharacterStyle::UseWindowFontColor)) { QBrush backbrush = bg; if (format.background() != Qt::NoBrush) { backbrush = format.background(); } QBrush frontBrush; frontBrush.setStyle(Qt::SolidPattern); // use the same luma calculation and threshold as msoffice - // see http://social.msdn.microsoft.com/Forums/en-US/os_binaryfile/thread/a02a9a24-efb6-4ba0-a187-0e3d2704882b + // see https://social.msdn.microsoft.com/Forums/en-US/os_binaryfile/thread/a02a9a24-efb6-4ba0-a187-0e3d2704882b int luma = ((5036060/2) * backbrush.color().red() + (9886846/2) * backbrush.color().green() + (1920103/2) * backbrush.color().blue()) >> 23; if (luma > 60) { frontBrush.setColor(QColor(Qt::black)); } else { frontBrush.setColor(QColor(Qt::white)); } format.setForeground(frontBrush); formatChanged = true; } if (formatChanged) { QTextLayout::FormatRange fr; fr.start = currentFragment.position() - block.position(); fr.length = currentFragment.length(); if (!format.hasProperty(KoCharacterStyle::InlineInstanceId)) { if (format.background().style() == Qt::NoBrush) { format.setBackground(QBrush(QColor(0, 0, 0, 0))); } if (format.foreground().style() == Qt::NoBrush) { format.setForeground(QBrush(QColor(0, 0, 0))); } } fr.format = format; // the prepend is done so the selections are at the end. selections.prepend(fr); } else { if (!format.hasProperty(KoCharacterStyle::InlineInstanceId)) { QTextLayout::FormatRange fr; fr.start = currentFragment.position() - block.position(); fr.length = currentFragment.length(); QTextCharFormat f; if (format.background().style() == Qt::NoBrush) { f.setBackground(QBrush(QColor(0, 0, 0, 0))); } else { f.setBackground(format.background()); } if (format.foreground().style() == Qt::NoBrush) { f.setForeground(QBrush(QColor(0, 0, 0))); } else { f.setForeground(format.foreground()); } fr.format = f; workaroundFormatRanges.append(fr); } } } } if (!selections.isEmpty()) { selections = workaroundFormatRanges + selections; } //We set clip because layout-draw doesn't clip text to it correctly after all //and adjust to make sure we don't clip edges of glyphs. The clipping is //important for paragraph split across two pages. //20pt enlargement seems safe as pages is split by 50pt and this helps unwanted //glyph cutting painter->setClipRect(br.adjusted(-20,-20,20,20), Qt::IntersectClip); layout->draw(painter, QPointF(0, 0), selections); if (context.showSectionBounds) { decorateParagraphSections(painter, block); } decorateParagraph(painter, block, context.showFormattingCharacters, context.showSpellChecking); painter->restore(); } else { if (lastBorder) { lastBorder->paint(*painter, lastBorderRect); lastBorder = 0; } } } if (lastBorder) { lastBorder->paint(*painter, lastBorderRect); } painter->translate(0, -d->verticalAlignOffset); painter->translate(0, bottom() - d->footNotesHeight); Q_FOREACH (KoTextLayoutNoteArea *footerArea, d->footNoteAreas) { footerArea->paint(painter, context); painter->translate(0, footerArea->bottom() - footerArea->top()); } painter->restore(); } void KoTextLayoutArea::drawListItem(QPainter *painter, QTextBlock &block) { KoTextBlockData blockData(block); QTextList *list = block.textList(); if (list && blockData.hasCounterData()) { QTextListFormat listFormat = list->format(); if (! blockData.counterText().isEmpty()) { QFont font(blockData.labelFormat().font(), d->documentLayout->paintDevice()); KoListStyle::Style listStyle = static_cast(listFormat.style()); QString result = blockData.counterText(); QTextLayout layout(result, font, d->documentLayout->paintDevice()); QList layouts; QTextLayout::FormatRange format; format.start = 0; format.length = blockData.counterText().length(); format.format = blockData.labelFormat(); layouts.append(format); layout.setAdditionalFormats(layouts); Qt::Alignment alignment = static_cast(listFormat.intProperty(KoListStyle::Alignment)); if (alignment == 0) { alignment = Qt::AlignLeft | Qt::AlignAbsolute; } if (d->isRtl && (alignment & Qt::AlignAbsolute) == 0) { if (alignment & Qt::AlignLeft) { alignment = Qt::AlignRight; } else if (alignment & Qt::AlignRight) { alignment = Qt::AlignLeft; } } alignment |= Qt::AlignAbsolute; QTextOption option(alignment); option.setTextDirection(block.layout()->textOption().textDirection()); /* if (option.textDirection() == Qt::RightToLeft || blockData.counterText().isRightToLeft()) { option.setAlignment(Qt::AlignRight); } */ layout.setTextOption(option); layout.beginLayout(); QTextLine line = layout.createLine(); line.setLineWidth(blockData.counterWidth()); layout.endLayout(); QPointF counterPosition = blockData.counterPosition(); if (block.layout()->lineCount() > 0) { // if there is text, then baseline align the counter. QTextLine firstParagLine = block.layout()->lineAt(0); if (KoListStyle::isNumberingStyle(listStyle)) { //if numbered list baseline align counterPosition += QPointF(0, firstParagLine.ascent() - layout.lineAt(0).ascent()); } else { //for unnumbered list center align counterPosition += QPointF(0, (firstParagLine.height() - layout.lineAt(0).height())/2.0); } } layout.draw(painter, counterPosition); //decorate the list label iff it is a numbered list if (KoListStyle::isNumberingStyle(listStyle)) { painter->save(); decorateListLabel(painter, blockData, layout.lineAt(0), block); painter->restore(); } } KoListStyle::Style listStyle = static_cast(listFormat.style()); if (listStyle == KoListStyle::ImageItem) { QFontMetricsF fm(blockData.labelFormat().font(), d->documentLayout->paintDevice()); qreal x = qMax(qreal(1), blockData.counterPosition().x()); qreal width = qMax(listFormat.doubleProperty(KoListStyle::Width), (qreal)1.0); qreal height = qMax(listFormat.doubleProperty(KoListStyle::Height), (qreal)1.0); qreal y = blockData.counterPosition().y() + fm.ascent() - fm.xHeight()/2 - height/2; // centered KoImageData *idata = listFormat.property(KoListStyle::BulletImage).value(); if (idata) { painter->drawPixmap(x, y, width, height, idata->pixmap()); } } } } void KoTextLayoutArea::decorateListLabel(QPainter *painter, const KoTextBlockData &blockData, const QTextLine &listLabelLine, const QTextBlock &listItem) { const QTextCharFormat listLabelCharFormat = blockData.labelFormat(); painter->setFont(listLabelCharFormat.font()); int startOfFragmentInBlock = 0; Q_ASSERT_X(listLabelLine.isValid(), __FUNCTION__, QString("Invalid list label").toLocal8Bit()); if (!listLabelLine.isValid()) { return; } int fragmentToLineOffset = 0; qreal x1 = blockData.counterPosition().x(); qreal x2 = listItem.layout()->lineAt(0).x(); if (x2 != x1) { drawStrikeOuts(painter, listLabelCharFormat, blockData.counterText(), listItem.layout()->lineAt(0), x1, x2, startOfFragmentInBlock, fragmentToLineOffset); drawOverlines(painter, listLabelCharFormat, blockData.counterText(), listItem.layout()->lineAt(0), x1, x2, startOfFragmentInBlock, fragmentToLineOffset); drawUnderlines(painter, listLabelCharFormat, blockData.counterText(), listItem.layout()->lineAt(0), x1, x2, startOfFragmentInBlock, fragmentToLineOffset); } } /** * Draw a line. Typically meant to underline text or similar. * @param painter the painter to paint on. * @param color the pen color to for the decoration line * @param type The type * @param style the type of line to draw. * @param width The thickness of the line, in pixels (the painter will be prescaled to points coordinate system). * @param x1 we are always drawing horizontal lines, this is the start point. * @param x2 we are always drawing horizontal lines, this is the end point. * @param y the y-offset to paint on. */ static void drawDecorationLine(QPainter *painter, const QColor &color, KoCharacterStyle::LineType type, KoCharacterStyle::LineStyle style, qreal width, const qreal x1, const qreal x2, const qreal y) { QPen penBackup = painter->pen(); QPen pen = painter->pen(); pen.setColor(color); pen.setWidthF(width); if (style == KoCharacterStyle::WaveLine) { // Ok, try the waves :) pen.setStyle(Qt::SolidLine); painter->setPen(pen); qreal x = x1; const qreal halfWaveWidth = 0.5 * width; const qreal halfWaveLength = 2 * width; const int startAngle = 0 * 16; const int middleAngle = 180 * 16; const int endAngle = 180 * 16; while (x < x2) { QRectF rectangle1(x, y, halfWaveLength, 2*halfWaveWidth); if (type == KoCharacterStyle::DoubleLine) { painter->translate(0, -pen.width()); painter->drawArc(rectangle1, startAngle, middleAngle); painter->translate(0, 2*pen.width()); painter->drawArc(rectangle1, startAngle, middleAngle); painter->translate(0, -pen.width()); } else { painter->drawArc(rectangle1, startAngle, middleAngle); } if (x + halfWaveLength > x2) break; QRectF rectangle2(x + halfWaveLength, y, halfWaveLength, 2*halfWaveWidth); if (type == KoCharacterStyle::DoubleLine) { painter->translate(0, -pen.width()); painter->drawArc(rectangle2, middleAngle, endAngle); painter->translate(0, 2*pen.width()); painter->drawArc(rectangle2, middleAngle, endAngle); painter->translate(0, -pen.width()); } else { painter->drawArc(rectangle2, middleAngle, endAngle); } x = x + 2 * halfWaveLength; } } else { if (style == KoCharacterStyle::LongDashLine) { QVector dashes; dashes << 12 << 2; pen.setDashPattern(dashes); } else { pen.setStyle((Qt::PenStyle)style); } painter->setPen(pen); if (type == KoCharacterStyle::DoubleLine) { painter->translate(0, -pen.width()); painter->drawLine(QPointF(x1, y), QPointF(x2, y)); painter->translate(0, 2*pen.width()); painter->drawLine(QPointF(x1, y), QPointF(x2, y)); painter->translate(0, -pen.width()); } else { painter->drawLine(QPointF(x1, y), QPointF(x2, y)); } } painter->setPen(penBackup); } static void drawDecorationText(QPainter *painter, const QTextLine &line, const QColor &color, const QString& decorText, qreal x1, qreal x2) { qreal y = line.position().y(); QPen oldPen = painter->pen(); painter->setPen(QPen(color)); do { QRectF br; painter->drawText(QRectF(QPointF(x1, y), QPointF(x2, y + line.height())), Qt::AlignLeft | Qt::AlignVCenter, decorText, &br); x1 = br.right(); } while (x1 <= x2); painter->setPen(oldPen); } static void drawDecorationWords(QPainter *painter, const QTextLine &line, const QString &text, const QColor &color, KoCharacterStyle::LineType type, KoCharacterStyle::LineStyle style, const QString& decorText, qreal width, const qreal y, const int fragmentToLineOffset, const int startOfFragmentInBlock) { qreal wordBeginX = -1; int j = line.textStart()+fragmentToLineOffset; while (j < line.textLength() + line.textStart() && j-startOfFragmentInBlockpen(); QPen pen = painter->pen(); pen.setWidth(1); pen.setColor(Qt::gray); painter->setPen(pen); qreal xl = layout->boundingRect().left(); qreal xr = qMax(layout->boundingRect().right(), layout->boundingRect().left() + width()); qreal yu = layout->boundingRect().top(); qreal yd = layout->boundingRect().bottom(); qreal bracketSize = painter->fontMetrics().height() / 2; const qreal levelShift = 3; QList openList = KoSectionUtils::sectionStartings(bf); for (int i = 0; i < openList.size(); i++) { int sectionLevel = openList[i]->level(); if (i == 0) { painter->drawLine(xl + sectionLevel * levelShift, yu, xr - sectionLevel * levelShift, yu); } painter->drawLine(xl + sectionLevel * levelShift, yu, xl + sectionLevel * levelShift, yu + bracketSize); painter->drawLine(xr - sectionLevel * levelShift, yu, xr - sectionLevel * levelShift, yu + bracketSize); } QList closeList = KoSectionUtils::sectionEndings(bf); for (int i = 0; i < closeList.size(); i++) { int sectionLevel = closeList[i]->correspondingSection()->level(); if (i == closeList.count() - 1) { painter->drawLine(xl + sectionLevel * levelShift, yd, xr - sectionLevel * levelShift, yd); } painter->drawLine(xl + sectionLevel * levelShift, yd, xl + sectionLevel * levelShift, yd - bracketSize); painter->drawLine(xr - sectionLevel * levelShift, yd, xr - sectionLevel * levelShift, yd - bracketSize); } painter->setPen(penBackup); } void KoTextLayoutArea::decorateParagraph(QPainter *painter, QTextBlock &block, bool showFormattingCharacters, bool showSpellChecking) { QTextLayout *layout = block.layout(); QTextBlockFormat bf = block.blockFormat(); QVariantList tabList = bf.property(KoParagraphStyle::TabPositions).toList(); QFont oldFont = painter->font(); QTextBlock::iterator it; int startOfBlock = -1; int currentTabStop = 0; // qDebug() << "\n-------------------" // << "\nGoing to decorate block\n" // << block.text() // << "\n-------------------"; // loop over text fragments in this paragraph and draw the underline and line through. for (it = block.begin(); !it.atEnd(); ++it) { QTextFragment currentFragment = it.fragment(); if (currentFragment.isValid()) { // qDebug() << "\tGoing to layout fragment:" << currentFragment.text(); QTextCharFormat fmt = currentFragment.charFormat(); painter->setFont(fmt.font()); // a block doesn't have a real start position, so use our own counter. Initialize // it with the position of the first text fragment in the block. if (startOfBlock == -1) { startOfBlock = currentFragment.position(); // start of this block w.r.t. the document } // the start of our fragment in the block is the absolute position of the fragment // in the document minus the start of the block in the document. int startOfFragmentInBlock = currentFragment.position() - startOfBlock; // a fragment can span multiple lines, but we paint the decorations per line. int firstLine = layout->lineForTextPosition(currentFragment.position() - startOfBlock).lineNumber(); int lastLine = layout->lineForTextPosition(currentFragment.position() + currentFragment.length() - startOfBlock).lineNumber(); // qDebug() << "\tfirst line:" << firstLine << "last line:" << lastLine; for (int i = firstLine ; i <= lastLine ; ++i) { QTextLine line = layout->lineAt(i); // qDebug() << "\n\t\tcurrent line:" << i // << "\n\t\tline length:" << line.textLength() << "width:"<< line.width() << "natural width" << line.naturalTextWidth() // << "\n\t\tvalid:" << layout->isValidCursorPosition(currentFragment.position() - startOfBlock) // << "\n\t\tcurrentFragment.position:" << currentFragment.position() // << "\n\t\tstartOfBlock:" << startOfBlock // << "\n\t\tstartOfFragmentInBlock:" << startOfFragmentInBlock; if (layout->isValidCursorPosition(currentFragment.position() - startOfBlock)) { // the start position for painting the decoration is the position of the fragment // inside, but after the first line, the decoration always starts at the beginning // of the line. See bug: 264471 int p1 = startOfFragmentInBlock; if (i > firstLine) { p1 = line.textStart(); } // qDebug() << "\n\t\tblock.text.length:" << block.text().length() << "p1" << p1; if (block.text().length() > p1 && block.text().at(p1) != QChar::ObjectReplacementCharacter) { Q_ASSERT_X(line.isValid(), __FUNCTION__, QString("Invalid line=%1 first=%2 last=%3").arg(i).arg(firstLine).arg(lastLine).toLocal8Bit()); // see bug 278682 if (!line.isValid()) continue; // end position: note that x2 can be smaller than x1 when we are handling RTL int p2 = startOfFragmentInBlock + currentFragment.length(); int lineEndWithoutPreedit = line.textStart() + line.textLength(); if (block.layout()->preeditAreaPosition() >= block.position() + line.textStart() && block.layout()->preeditAreaPosition() <= block.position() + line.textStart() + line.textLength()) { lineEndWithoutPreedit -= block.layout()->preeditAreaText().length(); } while (lineEndWithoutPreedit > line.textStart() && block.text().at(lineEndWithoutPreedit - 1) == ' ') { --lineEndWithoutPreedit; } if (lineEndWithoutPreedit < p2) { //line caps p2 = lineEndWithoutPreedit; } int fragmentToLineOffset = qMax(startOfFragmentInBlock - line.textStart(), 0); qreal x1 = line.cursorToX(p1); qreal x2 = line.cursorToX(p2); //qDebug() << "\n\t\t\tp1:" << p1 << "x1:" << x1 // << "\n\t\t\tp2:" << p2 << "x2:" << x2 // << "\n\t\t\tlineEndWithoutPreedit" << lineEndWithoutPreedit; if (x1 != x2) { drawStrikeOuts(painter, fmt, currentFragment.text(), line, x1, x2, startOfFragmentInBlock, fragmentToLineOffset); drawOverlines(painter, fmt, currentFragment.text(), line, x1, x2, startOfFragmentInBlock, fragmentToLineOffset); drawUnderlines(painter, fmt, currentFragment.text(), line, x1, x2, startOfFragmentInBlock, fragmentToLineOffset); } decorateTabsAndFormatting(painter, currentFragment, line, startOfFragmentInBlock, tabList, currentTabStop, showFormattingCharacters); // underline preedit strings if (layout->preeditAreaPosition() > -1) { int start = block.layout()->preeditAreaPosition(); int end = block.layout()->preeditAreaPosition() + block.layout()->preeditAreaText().length(); QTextCharFormat underline; underline.setFontUnderline(true); underline.setUnderlineStyle(QTextCharFormat::DashUnderline); //qDebug() << "underline style" << underline.underlineStyle(); //qDebug() << "line start: " << block.position() << line.textStart(); qreal z1 = 0; qreal z2 = 0; // preedit start in this line, end in this line if ( start >= block.position() + line.textStart() && end <= block.position() + line.textStart() + line.textLength() ) { z1 = line.cursorToX(start); z2 = line.cursorToX(end); } // preedit start in this line, end after this line if ( start >= block.position() + line.textStart() && end > block.position() + line.textStart() + line.textLength() ) { z1 = line.cursorToX(start); z2 = line.cursorToX(block.position() + line.textStart() + line.textLength()); } // preedit start before this line, end in this line if ( start < block.position() + line.textStart() && end <= block.position() + line.textStart() + line.textLength() ) { z1 = line.cursorToX(block.position() + line.textStart()); z2 = line.cursorToX(end); } // preedit start before this line, end after this line if ( start < block.position() + line.textStart() && end > block.position() + line.textStart() + line.textLength() ) { z1 = line.cursorToX(block.position() + line.textStart()); z2 = line.cursorToX(block.position() + line.textStart() + line.textLength()); } if (z2 > z1) { //qDebug() << "z1: " << z1 << "z2: " << z2; KoCharacterStyle::LineStyle fontUnderLineStyle = KoCharacterStyle::DashLine; KoCharacterStyle::LineType fontUnderLineType = KoCharacterStyle::SingleLine; QTextCharFormat::VerticalAlignment valign = fmt.verticalAlignment(); QFont font(fmt.font()); if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) font.setPointSize(font.pointSize() * 2 / 3); QFontMetricsF metrics(font, d->documentLayout->paintDevice()); qreal y = line.position().y(); if (valign == QTextCharFormat::AlignSubScript) y += line.height() - metrics.descent() + metrics.underlinePos(); else if (valign == QTextCharFormat::AlignSuperScript) y += metrics.ascent() + metrics.underlinePos(); else y += line.ascent() + metrics.underlinePos(); QColor color = fmt.foreground().color(); qreal width = computeWidth( // line thickness (KoCharacterStyle::LineWeight) underline.intProperty(KoCharacterStyle::UnderlineWeight), underline.doubleProperty(KoCharacterStyle::UnderlineWidth), font); if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) // adjust size. width = width * 2 / 3; drawDecorationLine(painter, color, fontUnderLineType, fontUnderLineStyle, width, z1, z2, y); } } } } } } } if (showFormattingCharacters) { QTextLine line = layout->lineForTextPosition(block.length()-1); qreal y = line.position().y() + line.ascent(); qreal x = line.cursorToX(block.length()-1); painter->drawText(QPointF(x, y), QChar((ushort)0x00B6)); } if (showSpellChecking) { // Finally let's paint our own spelling markings // TODO Should we make this optional at this point (right on/off handled by the plugin) // also we might want to provide alternative ways of drawing it KoTextBlockData blockData(block); QPen penBackup = painter->pen(); QPen pen; pen.setColor(QColor(Qt::red)); pen.setWidthF(1.5); QVector pattern; pattern << 1 << 2; pen.setDashPattern(pattern); painter->setPen(pen); QList::Iterator markIt = blockData.markupsBegin(KoTextBlockData::Misspell); QList::Iterator markEnd = blockData.markupsEnd(KoTextBlockData::Misspell); for (int i = 0 ; i < layout->lineCount(); ++i) { if (markIt == markEnd) { break; } QTextLine line = layout->lineAt(i); // the y position is placed half way between baseline and descent of the line // this is fast and sufficient qreal y = line.position().y() + line.ascent() + 0.5 * line.descent(); // first handle all those marking ranges that end on this line while (markIt != markEnd && markIt->lastChar < line.textStart() + line.textLength() && line.textStart() + line.textLength() <= block.length()) { if (!blockData.isMarkupsLayoutValid(KoTextBlockData::Misspell)) { if (markIt->firstChar > line.textStart()) { markIt->startX = line.cursorToX(markIt->firstChar); } markIt->endX = line.cursorToX(qMin(markIt->lastChar, block.length())); } qreal x1 = (markIt->firstChar > line.textStart()) ? markIt->startX : line.cursorToX(0); painter->drawLine(QPointF(x1, y), QPointF(markIt->endX, y)); ++markIt; } // there may be a markup range on this line that extends to the next line if (markIt != markEnd && markIt->firstChar < line.textStart() + line.textLength() && line.textStart() + line.textLength() <= block.length()) { if (!blockData.isMarkupsLayoutValid(KoTextBlockData::Misspell)) { if (markIt->firstChar > line.textStart()) { markIt->startX = line.cursorToX(markIt->firstChar); } } qreal x1 = (markIt->firstChar > line.textStart()) ? markIt->startX : line.cursorToX(0); painter->drawLine(QPointF(x1, y), QPointF(line.cursorToX(line.textStart() + line.textLength()), y)); // since it extends to next line we don't increment the iterator } } blockData.setMarkupsLayoutValidity(KoTextBlockData::Misspell, true); painter->setPen(penBackup); } painter->setFont(oldFont); } void KoTextLayoutArea::drawStrikeOuts(QPainter *painter, const QTextCharFormat ¤tCharFormat, const QString &text, const QTextLine &line, qreal x1, qreal x2, const int startOfFragmentInBlock, const int fragmentToLineOffset) const { KoCharacterStyle::LineStyle strikeOutStyle = (KoCharacterStyle::LineStyle) currentCharFormat.intProperty(KoCharacterStyle::StrikeOutStyle); KoCharacterStyle::LineType strikeOutType = (KoCharacterStyle::LineType) currentCharFormat.intProperty(KoCharacterStyle::StrikeOutType); if ((strikeOutStyle != KoCharacterStyle::NoLineStyle) && (strikeOutType != KoCharacterStyle::NoLineType)) { QTextCharFormat::VerticalAlignment valign = currentCharFormat.verticalAlignment(); QFont font(currentCharFormat.font()); if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) font.setPointSize(qRound(font.pointSize() * 2 / 3.)); QFontMetricsF metrics(font, d->documentLayout->paintDevice()); qreal y = line.position().y(); if (valign == QTextCharFormat::AlignSubScript) y += line.height() - metrics.descent() - metrics.strikeOutPos(); else if (valign == QTextCharFormat::AlignSuperScript) y += metrics.ascent() - metrics.strikeOutPos(); else y += line.ascent() - metrics.strikeOutPos(); QColor color = currentCharFormat.colorProperty(KoCharacterStyle::StrikeOutColor); if (!color.isValid()) color = currentCharFormat.foreground().color(); KoCharacterStyle::LineMode strikeOutMode = (KoCharacterStyle::LineMode) currentCharFormat.intProperty(KoCharacterStyle::StrikeOutMode); QString strikeOutText = currentCharFormat.stringProperty(KoCharacterStyle::StrikeOutText); qreal width = 0; // line thickness if (strikeOutText.isEmpty()) { width = computeWidth( (KoCharacterStyle::LineWeight) currentCharFormat.intProperty(KoCharacterStyle::StrikeOutWeight), currentCharFormat.doubleProperty(KoCharacterStyle::StrikeOutWidth), font); } if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) // adjust size. width = width * 2 / 3; if (strikeOutMode == KoCharacterStyle::SkipWhiteSpaceLineMode) { drawDecorationWords(painter, line, text, color, strikeOutType, strikeOutStyle, strikeOutText, width, y, fragmentToLineOffset, startOfFragmentInBlock); } else { if (strikeOutText.isEmpty()) drawDecorationLine(painter, color, strikeOutType, strikeOutStyle, width, x1, x2, y); else drawDecorationText(painter, line, color, strikeOutText, x1, x2); } } } void KoTextLayoutArea::drawOverlines(QPainter *painter, const QTextCharFormat ¤tCharFormat, const QString &text, const QTextLine &line, qreal x1, qreal x2, const int startOfFragmentInBlock, const int fragmentToLineOffset) const { KoCharacterStyle::LineStyle fontOverLineStyle = (KoCharacterStyle::LineStyle) currentCharFormat.intProperty(KoCharacterStyle::OverlineStyle); KoCharacterStyle::LineType fontOverLineType = (KoCharacterStyle::LineType) currentCharFormat.intProperty(KoCharacterStyle::OverlineType); if ((fontOverLineStyle != KoCharacterStyle::NoLineStyle) && (fontOverLineType != KoCharacterStyle::NoLineType)) { QTextCharFormat::VerticalAlignment valign = currentCharFormat.verticalAlignment(); QFont font(currentCharFormat.font()); if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) font.setPointSize(font.pointSize() * 2 / 3); QFontMetricsF metrics(font, d->documentLayout->paintDevice()); qreal y = line.position().y(); if (valign == QTextCharFormat::AlignSubScript) y += line.height() - metrics.descent() - metrics.overlinePos(); else if (valign == QTextCharFormat::AlignSuperScript) y += metrics.ascent() - metrics.overlinePos(); else y += line.ascent() - metrics.overlinePos(); QColor color = currentCharFormat.colorProperty(KoCharacterStyle::OverlineColor); if (!color.isValid()) color = currentCharFormat.foreground().color(); KoCharacterStyle::LineMode overlineMode = (KoCharacterStyle::LineMode) currentCharFormat.intProperty(KoCharacterStyle::OverlineMode); qreal width = computeWidth( // line thickness (KoCharacterStyle::LineWeight) currentCharFormat.intProperty(KoCharacterStyle::OverlineWeight), currentCharFormat.doubleProperty(KoCharacterStyle::OverlineWidth), font); if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) // adjust size. width = width * 2 / 3; if (overlineMode == KoCharacterStyle::SkipWhiteSpaceLineMode) { drawDecorationWords(painter, line, text, color, fontOverLineType, fontOverLineStyle, QString(), width, y, fragmentToLineOffset, startOfFragmentInBlock); } else { drawDecorationLine(painter, color, fontOverLineType, fontOverLineStyle, width, x1, x2, y); } } } void KoTextLayoutArea::drawUnderlines(QPainter *painter, const QTextCharFormat ¤tCharFormat,const QString &text, const QTextLine &line, qreal x1, qreal x2, const int startOfFragmentInBlock, const int fragmentToLineOffset) const { KoCharacterStyle::LineStyle fontUnderLineStyle = (KoCharacterStyle::LineStyle) currentCharFormat.intProperty(KoCharacterStyle::UnderlineStyle); KoCharacterStyle::LineType fontUnderLineType = (KoCharacterStyle::LineType) currentCharFormat.intProperty(KoCharacterStyle::UnderlineType); if ((fontUnderLineStyle != KoCharacterStyle::NoLineStyle) && (fontUnderLineType != KoCharacterStyle::NoLineType)) { QTextCharFormat::VerticalAlignment valign = currentCharFormat.verticalAlignment(); QFont font(currentCharFormat.font()); if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) font.setPointSize(font.pointSize() * 2 / 3); QFontMetricsF metrics(font, d->documentLayout->paintDevice()); qreal y = line.position().y(); if (valign == QTextCharFormat::AlignSubScript) y += line.height() - metrics.descent() + metrics.underlinePos(); else if (valign == QTextCharFormat::AlignSuperScript) y += metrics.ascent() + metrics.underlinePos(); else y += line.ascent() + metrics.underlinePos(); QColor color = currentCharFormat.underlineColor(); if (!color.isValid()) color = currentCharFormat.foreground().color(); KoCharacterStyle::LineMode underlineMode = (KoCharacterStyle::LineMode) currentCharFormat.intProperty(KoCharacterStyle::UnderlineMode); qreal width = computeWidth( // line thickness (KoCharacterStyle::LineWeight) currentCharFormat.intProperty(KoCharacterStyle::UnderlineWeight), currentCharFormat.doubleProperty(KoCharacterStyle::UnderlineWidth), font); if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) // adjust size. width = width * 2 / 3; if (underlineMode == KoCharacterStyle::SkipWhiteSpaceLineMode) { drawDecorationWords(painter, line, text, color, fontUnderLineType, fontUnderLineStyle, QString(), width, y, fragmentToLineOffset, startOfFragmentInBlock); } else { drawDecorationLine(painter, color, fontUnderLineType, fontUnderLineStyle, width, x1, x2, y); } } } // Decorate any tabs ('\t's) in 'currentFragment' and laid out in 'line'. int KoTextLayoutArea::decorateTabsAndFormatting(QPainter *painter, const QTextFragment& currentFragment, const QTextLine &line, const int startOfFragmentInBlock, const QVariantList& tabList, int currentTabStop, bool showFormattingCharacters) { // If a line in the layout represent multiple text fragments, this function will // be called multiple times on the same line, with different fragments. // Likewise, if a fragment spans two lines, then this function will be called twice // on the same fragment, once for each line. QString fragText = currentFragment.text(); QFontMetricsF fm(currentFragment.charFormat().font(), d->documentLayout->paintDevice()); qreal tabStyleLineMargin = fm.averageCharWidth() / 4; // leave some margin for the tab decoration line // currentFragment.position() : start of this fragment w.r.t. the document // startOfFragmentInBlock : start of this fragment w.r.t. the block // line.textStart() : start of this line w.r.t. the block int searchForCharFrom; // search for \t from this point onwards in fragText int searchForCharTill; // search for \t till this point in fragText if (line.textStart() >= startOfFragmentInBlock) { // fragment starts at or before the start of line // we are concerned with only that part of the fragment displayed in this line searchForCharFrom = line.textStart() - startOfFragmentInBlock; // It's a new line. So we should look at the first tab-stop properties for the next \t. currentTabStop = 0; } else { // fragment starts in the middle of the line searchForCharFrom = 0; } if (line.textStart() + line.textLength() > startOfFragmentInBlock + currentFragment.length()) { // fragment ends before the end of line. need to see only till the end of the fragment. searchForCharTill = currentFragment.length(); } else { // line ends before the fragment ends. need to see only till the end of this line. // but then, we need to convert the end of line to an index into fragText searchForCharTill = line.textLength() + line.textStart() - startOfFragmentInBlock; } for (int i = searchForCharFrom ; i < searchForCharTill; i++) { if (currentTabStop >= tabList.size() && !showFormattingCharacters) // no more decorations break; if (fragText[i] == '\t') { qreal x1(0.0); qreal x2(0.0); if (showFormattingCharacters) { x1 = line.cursorToX(startOfFragmentInBlock + i); x2 = line.cursorToX(startOfFragmentInBlock + i + 1); qreal y = line.position().y() + line.ascent() - fm.xHeight()/2.0; qreal arrowDim = fm.xHeight()/2.0; QPen penBackup = painter->pen(); QPen pen = painter->pen(); pen.setWidthF(fm.ascent()/10.0); pen.setStyle(Qt::SolidLine); painter->setPen(pen); painter->drawLine(QPointF(x1, y), QPointF(x2, y)); painter->drawLine(QPointF(x2 - arrowDim, y - arrowDim), QPointF(x2, y)); painter->drawLine(QPointF(x2 - arrowDim, y + arrowDim), QPointF(x2, y)); painter->setPen(penBackup); } if (currentTabStop < tabList.size()) { // still tabsstops worth examining if (!showFormattingCharacters) { // only then was it not calculated x1 = line.cursorToX(startOfFragmentInBlock + i); } // find a tab-stop decoration for this tab position // for eg., if there's a tab-stop at 1in, but the text before \t already spans 1.2in, // we should look at the next tab-stop KoText::Tab tab; do { tab = qvariant_cast(tabList[currentTabStop]); currentTabStop++; // comparing with x1 should work for all of left/right/center/char tabs } while (tab.position <= x1 && currentTabStop < tabList.size()); if (tab.position > x1) { if (!showFormattingCharacters) { // only then was it not calculated x2 = line.cursorToX(startOfFragmentInBlock + i + 1); } qreal tabStyleLeftLineMargin = tabStyleLineMargin; qreal tabStyleRightLineMargin = tabStyleLineMargin; // no margin if its adjacent char is also a tab if (i > searchForCharFrom && fragText[i-1] == '\t') tabStyleLeftLineMargin = 0; if (i < (searchForCharTill - 1) && fragText[i+1] == '\t') tabStyleRightLineMargin = 0; qreal y = line.position().y() + line.ascent() - 1; x1 += tabStyleLeftLineMargin; x2 -= tabStyleRightLineMargin; QColor tabDecorColor = currentFragment.charFormat().foreground().color(); if (tab.leaderColor.isValid()) tabDecorColor = tab.leaderColor; qreal width = computeWidth(tab.leaderWeight, tab.leaderWidth, painter->font()); if (x1 < x2) { if (tab.leaderText.isEmpty()) { drawDecorationLine(painter, tabDecorColor, tab.leaderType, tab.leaderStyle, width, x1, x2, y); } else { drawDecorationText(painter, line, tabDecorColor, tab.leaderText, x1, x2); } } } } } else if (showFormattingCharacters) { if (fragText[i] == ' ' || fragText[i] == QChar::Nbsp) { qreal x = line.cursorToX(startOfFragmentInBlock + i); qreal y = line.position().y() + line.ascent(); painter->drawText(QPointF(x, y), QChar((ushort)0xb7)); } else if (fragText[i] == QChar::LineSeparator){ qreal x = line.cursorToX(startOfFragmentInBlock + i); qreal y = line.position().y() + line.ascent(); painter->drawText(QPointF(x, y), QChar((ushort)0x21B5)); } } } return currentTabStop; } diff --git a/plugins/flake/textshape/textlayout/ListItemsHelper.cpp b/plugins/flake/textshape/textlayout/ListItemsHelper.cpp index aa30260f90..d3b2709dd3 100644 --- a/plugins/flake/textshape/textlayout/ListItemsHelper.cpp +++ b/plugins/flake/textshape/textlayout/ListItemsHelper.cpp @@ -1,503 +1,503 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007, 2010 Thomas Zander * Copyright (C) 2008 Girish Ramakrishnan * * 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 "ListItemsHelper.h" #include #include #include #include #include #include #include using namespace Lists; QString Lists::intToRoman(int n) { static const QString RNUnits[] = {"", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"}; static const QString RNTens[] = {"", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"}; static const QString RNHundreds[] = {"", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"}; static const QString RNThousands[] = {"", "m", "mm", "mmm", "mmmm", "mmmmm", "mmmmmm", "mmmmmmm", "mmmmmmmm", "mmmmmmmmm"}; if (n <= 0) { warnTextLayout << "intToRoman called with negative number: n=" << n; return QString::number(n); } return RNThousands[(n / 1000)] + RNHundreds[(n / 100) % 10 ] + RNTens[(n / 10) % 10 ] + RNUnits[(n) % 10 ]; } QString Lists::intToAlpha(int n, Capitalisation caps, bool letterSynchronization) { const char offset = caps == Uppercase ? 'A' : 'a'; QString answer; if (letterSynchronization) { int digits = 1; for (; n > 26; n -= 26) digits += 1; for (int i = 0; i < digits; i++) answer.prepend(QChar(offset + n - 1)); return answer; } else { char bottomDigit; while (n > 26) { bottomDigit = (n - 1) % 26; n = (n - 1) / 26; answer.prepend(QChar(offset + bottomDigit)); } } answer.prepend(QChar(offset + n - 1)); return answer; } QString Lists::intToScript(int n, KoListStyle::Style type) { // 10-base static const int bengali = 0x9e6; static const int gujarati = 0xae6; static const int gurumukhi = 0xa66; static const int kannada = 0xce6; static const int malayalam = 0xd66; static const int oriya = 0xb66; static const int tamil = 0x0be6; static const int telugu = 0xc66; static const int tibetan = 0xf20; static const int thai = 0xe50; int offset; switch (type) { case KoListStyle::Bengali: offset = bengali; break; case KoListStyle::Gujarati: offset = gujarati; break; case KoListStyle::Gurumukhi: offset = gurumukhi; break; case KoListStyle::Kannada: offset = kannada; break; case KoListStyle::Malayalam: offset = malayalam; break; case KoListStyle::Oriya: offset = oriya; break; case KoListStyle::Tamil: offset = tamil; break; case KoListStyle::Telugu: offset = telugu; break; case KoListStyle::Tibetan: offset = tibetan; break; case KoListStyle::Thai: offset = thai; break; default: return QString::number(n); } QString answer; while (n > 0) { answer.prepend(QChar(offset + n % 10)); n = n / 10; } return answer; } QString Lists::intToScriptList(int n, KoListStyle::Style type) { // 1 time Sequences // note; the leading X is to make these 1 based. static const char* const Abjad[] = { "أ", "ب", "ج", "د", "ﻫ", "و", "ز", "ح", "ط", "ي", "ك", "ل", "م", "ن", "س", "ع", "ف", "ص", "ق", "ر", "ش", "ت", "ث", "خ", "ذ", "ض", "ظ", "غ" }; static const char* const Abjad2[] = { "ﺃ", "ﺏ", "ﺝ", "ﺩ", "ﻫ", "ﻭ", "ﺯ", "ﺡ", "ﻁ", "ﻱ", "ﻙ", "ﻝ", "ﻡ", "ﻥ", "ﺹ", "ﻉ", "ﻑ", "ﺽ", "ﻕ", "ﺭ", "ﺱ", "ﺕ", "ﺙ", "ﺥ", "ﺫ", "ﻅ", "ﻍ", "ﺵ" }; static const char* const ArabicAlphabet[] = {"ا", "ب", "ت", "ث", "ج", "ح", "خ", "د", "ذ", "ر", "ز", "س", "ش", "ص", "ض", "ط", "ظ", "ع", "غ", "ف", "ق", "ك", "ل", "م", "ن", "ه", "و", "ي" }; /* // see this page for the 10, 100, 1000 etc http://en.wikipedia.org/wiki/Chinese_numerals static const char* const chinese1[] = { '零','壹','貳','叄','肆','伍','陸','柒','捌','玖' }; static const char* const chinese2[] = { '〇','一','二','三','四','五','六','七','八','九' }; - TODO: http://en.wikipedia.org/wiki/Korean_numerals - http://en.wikipedia.org/wiki/Japanese_numerals - 'http://en.wikipedia.org/wiki/Hebrew_numerals' - 'http://en.wikipedia.org/wiki/Armenian_numerals' - 'http://en.wikipedia.org/wiki/Greek_numerals' - 'http://en.wikipedia.org/wiki/Cyrillic_numerals' - 'http://en.wikipedia.org/wiki/Sanskrit_numerals' - 'http://en.wikipedia.org/wiki/Ge%27ez_alphabet#Numerals' - 'http://en.wikipedia.org/wiki/Abjad_numerals' + TODO: https://en.wikipedia.org/wiki/Korean_numerals + https://en.wikipedia.org/wiki/Japanese_numerals + 'https://en.wikipedia.org/wiki/Hebrew_numerals' + 'https://en.wikipedia.org/wiki/Armenian_numerals' + 'https://en.wikipedia.org/wiki/Greek_numerals' + 'https://en.wikipedia.org/wiki/Cyrillic_numerals' + 'https://en.wikipedia.org/wiki/Sanskrit_numerals' + 'https://en.wikipedia.org/wiki/Ge%27ez_alphabet#Numerals' + 'https://en.wikipedia.org/wiki/Abjad_numerals' */ switch (type) { case KoListStyle::Abjad: if (n > 22) return "*"; return QString::fromUtf8(Abjad[n-1]); case KoListStyle::AbjadMinor: if (n > 22) return "*"; return QString::fromUtf8(Abjad2[n-1]); case KoListStyle::ArabicAlphabet: if (n > 28) return "*"; return QString::fromUtf8(ArabicAlphabet[n-1]); default: return QString::number(n); } } QString Lists::intToNumberingStyle(int index, KoListStyle::Style listStyle, bool letterSynchronization) { QString counterText; switch(listStyle) { case KoListStyle::DecimalItem: counterText = QString::number(index); break; case KoListStyle::AlphaLowerItem: counterText = intToAlpha(index, Lowercase, letterSynchronization); break; case KoListStyle::UpperAlphaItem: counterText = intToAlpha(index, Uppercase, letterSynchronization); break; case KoListStyle::RomanLowerItem: counterText = intToRoman(index); break; case KoListStyle::UpperRomanItem: counterText = intToRoman(index).toUpper(); break; case KoListStyle::Bengali: case KoListStyle::Gujarati: case KoListStyle::Gurumukhi: case KoListStyle::Kannada: case KoListStyle::Malayalam: case KoListStyle::Oriya: case KoListStyle::Tamil: case KoListStyle::Telugu: case KoListStyle::Tibetan: case KoListStyle::Thai: counterText = intToScript(index, listStyle); break; case KoListStyle::Abjad: case KoListStyle::ArabicAlphabet: case KoListStyle::AbjadMinor: counterText = intToScriptList(index, listStyle); break; default: counterText = QString::number(index); } return counterText; } QList Lists::genericListStyleItems() { QList answer; answer.append(ListStyleItem(i18nc("Text list-style", "None"), KoListStyle::None)); answer.append(ListStyleItem(i18n("Small Bullet"), KoListStyle::Bullet)); answer.append(ListStyleItem(i18n("Circle Bullet"), KoListStyle::CircleItem)); answer.append(ListStyleItem(i18n("Square Bullet"), KoListStyle::SquareItem)); answer.append(ListStyleItem(i18n("Rhombus Bullet"), KoListStyle::RhombusItem)); answer.append(ListStyleItem(i18n("Check Mark Bullet"), KoListStyle::HeavyCheckMarkItem)); answer.append(ListStyleItem(i18n("Rightwards Arrow Bullet"), KoListStyle::RightArrowItem)); answer.append(ListStyleItem(i18n("Arabic"), KoListStyle::DecimalItem)); answer.append(ListStyleItem(i18n("Lower Alphabetical"), KoListStyle::AlphaLowerItem)); answer.append(ListStyleItem(i18n("Upper Alphabetical"), KoListStyle::UpperAlphaItem)); answer.append(ListStyleItem(i18n("Lower Roman"), KoListStyle::RomanLowerItem)); answer.append(ListStyleItem(i18n("Upper Roman"), KoListStyle::UpperRomanItem)); return answer; } QList Lists::otherListStyleItems() { QList answer; answer.append(ListStyleItem(i18n("Large Bullet"), KoListStyle::BlackCircle)); answer.append(ListStyleItem(i18n("Ballot X Bullet"), KoListStyle::BallotXItem)); answer.append(ListStyleItem(i18n("Rightwards Arrow Head Bullet"), KoListStyle::RightArrowHeadItem)); answer.append(ListStyleItem(i18n("Bengali"), KoListStyle::Bengali)); answer.append(ListStyleItem(i18n("Gujarati"), KoListStyle::Gujarati)); answer.append(ListStyleItem(i18n("Gurumukhi"), KoListStyle::Gurumukhi)); answer.append(ListStyleItem(i18n("Kannada"), KoListStyle::Kannada)); answer.append(ListStyleItem(i18n("Malayalam"), KoListStyle::Malayalam)); answer.append(ListStyleItem(i18n("Oriya"), KoListStyle::Oriya)); answer.append(ListStyleItem(i18n("Tamil"), KoListStyle::Tamil)); answer.append(ListStyleItem(i18n("Telugu"), KoListStyle::Telugu)); answer.append(ListStyleItem(i18n("Tibetan"), KoListStyle::Tibetan)); answer.append(ListStyleItem(i18n("Thai"), KoListStyle::Thai)); answer.append(ListStyleItem(i18n("Abjad"), KoListStyle::Abjad)); answer.append(ListStyleItem(i18n("AbjadMinor"), KoListStyle::AbjadMinor)); answer.append(ListStyleItem(i18n("ArabicAlphabet"), KoListStyle::ArabicAlphabet)); answer.append(ListStyleItem(i18n("Image"), KoListStyle::ImageItem)); return answer; } // ------------------- ListItemsHelper ------------ /// \internal helper class for calculating text-lists prefixes and indents ListItemsHelper::ListItemsHelper(QTextList *textList, const QFont &font) : m_textList(textList) , m_fm(font, textList->document()->documentLayout()->paintDevice()) { } void ListItemsHelper::recalculateBlock(QTextBlock &block) { //warnTextLayout; const QTextListFormat format = m_textList->format(); const KoListStyle::Style listStyle = static_cast(format.style()); const QString prefix = format.stringProperty(KoListStyle::ListItemPrefix); const QString suffix = format.stringProperty(KoListStyle::ListItemSuffix); const int level = format.intProperty(KoListStyle::Level); int dp = format.intProperty(KoListStyle::DisplayLevel); if (dp > level) dp = level; const int displayLevel = dp ? dp : 1; QTextBlockFormat blockFormat = block.blockFormat(); // Look if we have a block that is inside a header. We need to special case them cause header-lists are // different from any other kind of list and they do build up there own global list (table of content). bool isOutline = blockFormat.intProperty(KoParagraphStyle::OutlineLevel) > 0; int startValue = 1; if (format.hasProperty(KoListStyle::StartValue)) startValue = format.intProperty(KoListStyle::StartValue); int index = startValue; bool fixed = false; if (blockFormat.boolProperty(KoParagraphStyle::RestartListNumbering)) { index = format.intProperty(KoListStyle::StartValue); fixed = true; } const int paragIndex = blockFormat.intProperty(KoParagraphStyle::ListStartValue); if (paragIndex > 0) { index = paragIndex; fixed = true; } if (!fixed) { //if this is the first item then find if the list has to be continued from any other list KoList *listContinued = 0; if (m_textList->itemNumber(block) == 0 && KoTextDocument(m_textList->document()).list(m_textList) && (listContinued = KoTextDocument(m_textList->document()).list(m_textList)->listContinuedFrom())) { //find the previous list of the same level QTextList *previousTextList = listContinued->textLists().at(level - 1).data(); if (previousTextList) { QTextBlock textBlock = previousTextList->item(previousTextList->count() - 1); if (textBlock.isValid()) { index = KoTextBlockData(textBlock).counterIndex() + 1; //resume the previous list count } } } else if (m_textList->itemNumber(block) > 0) { QTextBlock textBlock = m_textList->item(m_textList->itemNumber(block) - 1); if (textBlock.isValid()) { index = KoTextBlockData(textBlock).counterIndex() + 1; //resume the previous list count } } } qreal width = 0.0; KoTextBlockData blockData(block); if (blockFormat.boolProperty(KoParagraphStyle::UnnumberedListItem) || blockFormat.boolProperty(KoParagraphStyle::IsListHeader)) { blockData.setCounterPlainText(QString()); blockData.setCounterPrefix(QString()); blockData.setCounterSuffix(QString()); blockData.setPartialCounterText(QString()); // set the counter for the current un-numbered list to the counter index of the previous list item. // index-1 because the list counter would have already incremented by one blockData.setCounterIndex(index - 1); if (blockFormat.boolProperty(KoParagraphStyle::IsListHeader)) { blockData.setCounterWidth(format.doubleProperty(KoListStyle::MinimumWidth)); blockData.setCounterSpacing(0); } return; } QString item; if (displayLevel > 1) { int checkLevel = level; int tmpDisplayLevel = displayLevel; bool counterResetRequired = true; for (QTextBlock b = block.previous(); tmpDisplayLevel > 1 && b.isValid(); b = b.previous()) { if (b.textList() == 0) continue; QTextListFormat lf = b.textList()->format(); if (lf.property(KoListStyle::StyleId) != format.property(KoListStyle::StyleId)) continue; // uninteresting for us if (isOutline != bool(b.blockFormat().intProperty(KoParagraphStyle::OutlineLevel))) continue; // also uninteresting cause the one is an outline-listitem while the other is not if (! KoListStyle::isNumberingStyle(static_cast(lf.style()))) { continue; } if (b.blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) { continue; //unnumbered listItems are irrelevant } const int otherLevel = lf.intProperty(KoListStyle::Level); if (isOutline && checkLevel == otherLevel) { counterResetRequired = false; } if (checkLevel <= otherLevel) continue; KoTextBlockData otherData(b); if (!otherData.hasCounterData()) { continue; } if (tmpDisplayLevel - 1 < otherLevel) { // can't just copy it fully since we are // displaying less then the full counter item += otherData.partialCounterText(); tmpDisplayLevel--; checkLevel--; for (int i = otherLevel + 1; i < level; i++) { tmpDisplayLevel--; item += "." + intToNumberingStyle(index, listStyle, m_textList->format().boolProperty(KoListStyle::LetterSynchronization)); // add missing counters. } } else { // just copy previous counter as prefix QString otherPrefix = lf.stringProperty(KoListStyle::ListItemPrefix); QString otherSuffix = lf.stringProperty(KoListStyle::ListItemSuffix); QString pureCounter = otherData.counterText().mid(otherPrefix.size()); pureCounter = pureCounter.left(pureCounter.size() - otherSuffix.size()); item += pureCounter; for (int i = otherLevel + 1; i < level; i++) item += "." + intToNumberingStyle(index, listStyle, m_textList->format().boolProperty(KoListStyle::LetterSynchronization)); // add missing counters. tmpDisplayLevel = 0; if (isOutline && counterResetRequired) { index = 1; } break; } } for (int i = 1; i < tmpDisplayLevel; i++) item = intToNumberingStyle(index, listStyle, m_textList->format().boolProperty(KoListStyle::LetterSynchronization)) + "." + item; // add missing counters. } if ((listStyle == KoListStyle::DecimalItem || listStyle == KoListStyle::AlphaLowerItem || listStyle == KoListStyle::UpperAlphaItem || listStyle == KoListStyle::RomanLowerItem || listStyle == KoListStyle::UpperRomanItem) && !(item.isEmpty() || item.endsWith('.') || item.endsWith(' '))) { item += '.'; } bool calcWidth = true; QString partialCounterText; if (KoListStyle::isNumberingStyle(listStyle)) { partialCounterText = intToNumberingStyle(index, listStyle, m_textList->format().boolProperty(KoListStyle::LetterSynchronization)); } else { switch (listStyle) { case KoListStyle::SquareItem: case KoListStyle::Bullet: case KoListStyle::BlackCircle: case KoListStyle::DiscItem: case KoListStyle::CircleItem: case KoListStyle::HeavyCheckMarkItem: case KoListStyle::BallotXItem: case KoListStyle::RightArrowItem: case KoListStyle::RightArrowHeadItem: case KoListStyle::RhombusItem: case KoListStyle::BoxItem: { calcWidth = false; if (format.intProperty(KoListStyle::BulletCharacter)) item = QString(QChar(format.intProperty(KoListStyle::BulletCharacter))); width = m_fm.width(item); int percent = format.intProperty(KoListStyle::RelativeBulletSize); if (percent > 0) width = width * (percent / 100.0); break; } case KoListStyle::CustomCharItem: calcWidth = false; if (format.intProperty(KoListStyle::BulletCharacter)) item = QString(QChar(format.intProperty(KoListStyle::BulletCharacter))); width = m_fm.width(item); break; case KoListStyle::None: calcWidth = false; width = 0.0; break; case KoListStyle::ImageItem: calcWidth = false; width = qMax(format.doubleProperty(KoListStyle::Width), (qreal)1.0); break; default: // others we ignore. calcWidth = false; } } blockData.setCounterIsImage(listStyle == KoListStyle::ImageItem); blockData.setPartialCounterText(partialCounterText); blockData.setCounterIndex(index); item += partialCounterText; blockData.setCounterPlainText(item); blockData.setCounterPrefix(prefix); blockData.setCounterSuffix(suffix); if (calcWidth) width = m_fm.width(item); index++; width += m_fm.width(prefix + suffix); qreal counterSpacing = 0; if (format.boolProperty(KoListStyle::AlignmentMode)) { // for AlignmentMode spacing should be 0 counterSpacing = 0; } else { if (listStyle != KoListStyle::None) { // see ODF spec 1.2 item 20.422 counterSpacing = format.doubleProperty(KoListStyle::MinimumDistance); if (width < format.doubleProperty(KoListStyle::MinimumWidth)) { counterSpacing -= format.doubleProperty(KoListStyle::MinimumWidth) - width; } counterSpacing = qMax(counterSpacing, qreal(0.0)); } width = qMax(width, format.doubleProperty(KoListStyle::MinimumWidth)); } blockData.setCounterWidth(width); blockData.setCounterSpacing(counterSpacing); //warnTextLayout; } // static bool ListItemsHelper::needsRecalc(QTextList *textList) { Q_ASSERT(textList); QTextBlock tb = textList->item(0); KoTextBlockData blockData(tb); return !blockData.hasCounterData(); } diff --git a/plugins/impex/CMakeLists.txt b/plugins/impex/CMakeLists.txt index 80961161b5..499b1c97d0 100644 --- a/plugins/impex/CMakeLists.txt +++ b/plugins/impex/CMakeLists.txt @@ -1,56 +1,56 @@ project(kritafilters) add_subdirectory(libkra) if(CMAKE_SIZEOF_VOID_P EQUAL 4) add_definitions( -DCPU_32_BITS ) endif() if(JPEG_FOUND AND HAVE_LCMS2) add_subdirectory(jpeg) endif() if(TIFF_FOUND) add_subdirectory(tiff) endif() if(PNG_FOUND) add_subdirectory(png) add_subdirectory(csv) endif() if(OPENEXR_FOUND) add_subdirectory(exr) endif() if(Poppler_Qt5_FOUND) add_subdirectory(pdf) endif() -if(OpenJPEG_FOUND) +if(OpenJPEG_FOUND AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") add_subdirectory(jp2) endif() if(LIBRAW_FOUND) add_subdirectory(raw) endif() add_subdirectory(svg) add_subdirectory(qimageio) add_subdirectory(ora) add_subdirectory(xcf) add_subdirectory(psd) add_subdirectory(qml) add_subdirectory(tga) add_subdirectory(heightmap) add_subdirectory(brush) add_subdirectory(spriter) add_subdirectory(kra) if (GIF_FOUND) add_subdirectory(gif) endif() if (HEIF_FOUND) add_subdirectory(heif) endif() diff --git a/plugins/impex/csv/csv_layer_record.cpp b/plugins/impex/csv/csv_layer_record.cpp index 7143aee9f0..522a422ac1 100644 --- a/plugins/impex/csv/csv_layer_record.cpp +++ b/plugins/impex/csv/csv_layer_record.cpp @@ -1,31 +1,28 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "csv_layer_record.h" CSVLayerRecord::CSVLayerRecord() - : layer(0) - , channel(0) - , frame(0) { } CSVLayerRecord::~CSVLayerRecord() { } diff --git a/plugins/impex/csv/csv_layer_record.h b/plugins/impex/csv/csv_layer_record.h index 24a47838ec..5be3c4024d 100644 --- a/plugins/impex/csv/csv_layer_record.h +++ b/plugins/impex/csv/csv_layer_record.h @@ -1,45 +1,45 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef CSV_LAYER_RECORD_H_ #define CSV_LAYER_RECORD_H_ #include #include "kis_raster_keyframe_channel.h" class CSVLayerRecord { public: CSVLayerRecord(); virtual ~CSVLayerRecord(); QString name; QString blending; - float density; - int visible; + float density {0.0}; + int visible {0}; - KisLayer* layer; - KisRasterKeyframeChannel *channel; + KisLayer* layer {0}; + KisRasterKeyframeChannel *channel {0}; QString last; QString path; - int frame; + int frame {0}; }; #endif diff --git a/plugins/impex/csv/tests/CMakeLists.txt b/plugins/impex/csv/tests/CMakeLists.txt index dd7a4193ff..68efd0974a 100644 --- a/plugins/impex/csv/tests/CMakeLists.txt +++ b/plugins/impex/csv/tests/CMakeLists.txt @@ -1,11 +1,12 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() +include(KritaAddBrokenUnitTest) -ecm_add_test(kis_csv_test.cpp +krita_add_broken_unit_test(kis_csv_test.cpp TEST_NAME kis_csv_test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/csv/tests/data/writeonlyFile.txt b/plugins/impex/csv/tests/data/writeonlyFile.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/plugins/impex/jp2/tests/CMakeLists.txt b/plugins/impex/jp2/tests/CMakeLists.txt index 596045d947..9ebdaae8d6 100644 --- a/plugins/impex/jp2/tests/CMakeLists.txt +++ b/plugins/impex/jp2/tests/CMakeLists.txt @@ -1,11 +1,11 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() -ecm_add_test(KisJP2Test.cpp +krita_add_broken_unit_test(KisJP2Test.cpp TEST_NAME KisJP2Test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/kra/kra_converter.cpp b/plugins/impex/kra/kra_converter.cpp index 55e20d7b40..8f9618f226 100644 --- a/plugins/impex/kra/kra_converter.cpp +++ b/plugins/impex/kra/kra_converter.cpp @@ -1,407 +1,437 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kra_converter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include static const char CURRENT_DTD_VERSION[] = "2.0"; KraConverter::KraConverter(KisDocument *doc) : m_doc(doc) , m_image(doc->savingImage()) { } KraConverter::KraConverter(KisDocument *doc, QPointer updater) : m_doc(doc) , m_image(doc->savingImage()) , m_updater(updater) { } KraConverter::~KraConverter() { delete m_store; delete m_kraSaver; delete m_kraLoader; } +void fixCloneLayers(KisImageSP image, KisNodeSP root) +{ + KisNodeSP first = root->firstChild(); + KisNodeSP node = first; + while (!node.isNull()) { + if (node->inherits("KisCloneLayer")) { + KisCloneLayer* layer = dynamic_cast(node.data()); + if (layer && layer->copyFrom().isNull()) { + KisLayerSP reincarnation = layer->reincarnateAsPaintLayer(); + image->addNode(reincarnation, node->parent(), node->prevSibling()); + image->removeNode(node); + node = reincarnation; + } + } else if (node->childCount() > 0) { + fixCloneLayers(image, node); + } + node = node->nextSibling(); + } +} + KisImportExportErrorCode KraConverter::buildImage(QIODevice *io) { m_store = KoStore::createStore(io, KoStore::Read, "", KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Not a valid Krita file")); return ImportExportCodes::FileFormatIncorrect; } - bool success; + bool success = false; { if (m_store->hasFile("root") || m_store->hasFile("maindoc.xml")) { // Fallback to "old" file format (maindoc.xml) KoXmlDocument doc; KisImportExportErrorCode res = oldLoadAndParse(m_store, "root", doc); if (res.isOk()) res = loadXML(doc, m_store); if (!res.isOk()) { return res; } } else { errUI << "ERROR: No maindoc.xml" << endl; m_doc->setErrorMessage(i18n("Invalid document: no file 'maindoc.xml'.")); return ImportExportCodes::FileFormatIncorrect; } if (m_store->hasFile("documentinfo.xml")) { KoXmlDocument doc; - if (oldLoadAndParse(m_store, "documentinfo.xml", doc).isOk()) { + KisImportExportErrorCode resultHere = oldLoadAndParse(m_store, "documentinfo.xml", doc); + if (resultHere.isOk()) { m_doc->documentInfo()->load(doc); } } success = completeLoading(m_store); } + fixCloneLayers(m_image, m_image->root()); + return success ? ImportExportCodes::OK : ImportExportCodes::Failure; } KisImageSP KraConverter::image() { return m_image; } vKisNodeSP KraConverter::activeNodes() { return m_activeNodes; } QList KraConverter::assistants() { return m_assistants; } KisImportExportErrorCode KraConverter::buildFile(QIODevice *io, const QString &filename) { setProgress(5); m_store = KoStore::createStore(io, KoStore::Write, m_doc->nativeFormatMimeType(), KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Could not create the file for saving")); return ImportExportCodes::CannotCreateFile; } setProgress(20); m_kraSaver = new KisKraSaver(m_doc, filename); KisImportExportErrorCode resultCode = saveRootDocuments(m_store); if (!resultCode.isOk()) { return resultCode; } setProgress(40); bool result; result = m_kraSaver->saveKeyframes(m_store, m_doc->url().toLocalFile(), true); if (!result) { qWarning() << "saving key frames failed"; } setProgress(60); result = m_kraSaver->saveBinaryData(m_store, m_image, m_doc->url().toLocalFile(), true, m_doc->isAutosaving()); if (!result) { qWarning() << "saving binary data failed"; } setProgress(70); result = m_kraSaver->savePalettes(m_store, m_image, m_doc->url().toLocalFile()); if (!result) { qWarning() << "saving palettes data failed"; } setProgress(80); if (!m_store->finalize()) { return ImportExportCodes::Failure; } if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); return ImportExportCodes::Failure; } setProgress(90); return ImportExportCodes::OK; } KisImportExportErrorCode KraConverter::saveRootDocuments(KoStore *store) { dbgFile << "Saving root"; if (store->open("root")) { KoStoreDevice dev(store); if (!saveToStream(&dev) || !store->close()) { dbgUI << "saveToStream failed"; return ImportExportCodes::NoAccessToWrite; } } else { m_doc->setErrorMessage(i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml"))); return ImportExportCodes::ErrorWhileWriting; } if (store->open("documentinfo.xml")) { QDomDocument doc = KisDocument::createDomDocument("document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1"); doc = m_doc->documentInfo()->save(doc); KoStoreDevice dev(store); QByteArray s = doc.toByteArray(); // this is already Utf8! bool success = dev.write(s.data(), s.size()); if (!success) { return ImportExportCodes::ErrorWhileWriting; } store->close(); } else { return ImportExportCodes::Failure; } if (store->open("preview.png")) { // ### TODO: missing error checking (The partition could be full!) KisImportExportErrorCode result = savePreview(store); (void)store->close(); if (!result.isOk()) { return result; } } else { return ImportExportCodes::Failure; } dbgUI << "Saving done of url:" << m_doc->url().toLocalFile(); return ImportExportCodes::OK; } bool KraConverter::saveToStream(QIODevice *dev) { QDomDocument doc = createDomDocument(); // Save to buffer QByteArray s = doc.toByteArray(); // utf8 already dev->open(QIODevice::WriteOnly); int nwritten = dev->write(s.data(), s.size()); if (nwritten != (int)s.size()) { warnUI << "wrote " << nwritten << "- expected" << s.size(); } return nwritten == (int)s.size(); } QDomDocument KraConverter::createDomDocument() { QDomDocument doc = m_doc->createDomDocument("DOC", CURRENT_DTD_VERSION); QDomElement root = doc.documentElement(); root.setAttribute("editor", "Krita"); root.setAttribute("syntaxVersion", "2"); root.setAttribute("kritaVersion", KritaVersionWrapper::versionString(false)); root.appendChild(m_kraSaver->saveXML(doc, m_image)); if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); } return doc; } KisImportExportErrorCode KraConverter::savePreview(KoStore *store) { QPixmap pix = m_doc->generatePreview(QSize(256, 256)); QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly)); if (preview.size() == QSize(0,0)) { QSize newSize = m_doc->savingImage()->bounds().size(); newSize.scale(QSize(256, 256), Qt::KeepAspectRatio); preview = QImage(newSize, QImage::Format_ARGB32); preview.fill(QColor(0, 0, 0, 0)); } KoStoreDevice io(store); if (!io.open(QIODevice::WriteOnly)) { return ImportExportCodes::NoAccessToWrite; } bool ret = preview.save(&io, "PNG"); io.close(); return ret ? ImportExportCodes::OK : ImportExportCodes::ErrorWhileWriting; } KisImportExportErrorCode KraConverter::oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc) { //dbgUI <<"Trying to open" << filename; if (!store->open(filename)) { warnUI << "Entry " << filename << " not found!"; m_doc->setErrorMessage(i18n("Could not find %1", filename)); return ImportExportCodes::FileNotExist; } // Error variables for QDomDocument::setContent QString errorMsg; int errorLine, errorColumn; bool ok = xmldoc.setContent(store->device(), &errorMsg, &errorLine, &errorColumn); store->close(); if (!ok) { errUI << "Parsing error in " << filename << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; m_doc->setErrorMessage(i18n("Parsing error in %1 at line %2, column %3\nError message: %4", filename, errorLine, errorColumn, QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0))); return ImportExportCodes::FileFormatIncorrect; } dbgUI << "File" << filename << " loaded and parsed"; return ImportExportCodes::OK; } KisImportExportErrorCode KraConverter::loadXML(const KoXmlDocument &doc, KoStore *store) { Q_UNUSED(store); KoXmlElement root; KoXmlNode node; if (doc.doctype().name() != "DOC") { errUI << "The format is not supported or the file is corrupted"; m_doc->setErrorMessage(i18n("The format is not supported or the file is corrupted")); return ImportExportCodes::FileFormatIncorrect; } root = doc.documentElement(); int syntaxVersion = root.attribute("syntaxVersion", "3").toInt(); if (syntaxVersion > 2) { errUI << "The file is too new for this version of Krita:" << syntaxVersion; m_doc->setErrorMessage(i18n("The file is too new for this version of Krita (%1).", syntaxVersion)); return ImportExportCodes::FormatFeaturesUnsupported; } if (!root.hasChildNodes()) { errUI << "The file has no layers."; m_doc->setErrorMessage(i18n("The file has no layers.")); return ImportExportCodes::FileFormatIncorrect; } m_kraLoader = new KisKraLoader(m_doc, syntaxVersion); // reset the old image before loading the next one m_doc->setCurrentImage(0, false); for (node = root.firstChild(); !node.isNull(); node = node.nextSibling()) { if (node.isElement()) { if (node.nodeName() == "IMAGE") { KoXmlElement elem = node.toElement(); if (!(m_image = m_kraLoader->loadXML(elem))) { if (m_kraLoader->errorMessages().isEmpty()) { errUI << "Unknown error while opening the .kra file."; m_doc->setErrorMessage(i18n("Unknown error.")); } else { m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); errUI << m_kraLoader->errorMessages().join("\n"); } return ImportExportCodes::Failure; } // HACK ALERT! m_doc->hackPreliminarySetImage(m_image); return ImportExportCodes::OK; } else { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("The file does not contain an image.")); } return ImportExportCodes::FileFormatIncorrect; } } } return ImportExportCodes::Failure; } bool KraConverter::completeLoading(KoStore* store) { if (!m_image) { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("Unknown error.")); } else { m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); } return false; } m_image->blockUpdates(); QString layerPathName = m_kraLoader->imageName(); if (!m_store->hasDirectory(layerPathName)) { // We might be hitting an encoding problem. Get the only folder in the toplevel Q_FOREACH (const QString &entry, m_store->directoryList()) { if (entry.contains("/layers/")) { layerPathName = entry.split("/layers/").first(); m_store->setSubstitution(m_kraLoader->imageName(), layerPathName); break; } } } m_kraLoader->loadBinaryData(store, m_image, m_doc->localFilePath(), true); m_kraLoader->loadPalettes(store, m_doc); + if (!m_kraLoader->errorMessages().isEmpty()) { + m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); + return false; + } + m_image->unblockUpdates(); if (!m_kraLoader->warningMessages().isEmpty()) { // warnings do not interrupt loading process, so we do not return here m_doc->setWarningMessage(m_kraLoader->warningMessages().join("\n")); } m_activeNodes = m_kraLoader->selectedNodes(); m_assistants = m_kraLoader->assistants(); return true; + return m_kraLoader->errorMessages().isEmpty(); } void KraConverter::cancel() { m_stop = true; } void KraConverter::setProgress(int progress) { if (m_updater) { m_updater->setProgress(progress); } } diff --git a/plugins/impex/libkra/kis_kra_load_visitor.cpp b/plugins/impex/libkra/kis_kra_load_visitor.cpp index c11dc50054..d495abd1ff 100644 --- a/plugins/impex/libkra/kis_kra_load_visitor.cpp +++ b/plugins/impex/libkra/kis_kra_load_visitor.cpp @@ -1,784 +1,789 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_kra_load_visitor.h" #include "kis_kra_tags.h" #include "flake/kis_shape_layer.h" #include "flake/KisReferenceImagesLayer.h" #include "KisReferenceImage.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // kritaimage #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_transform_mask_params_factory_registry.h" #include #include #include #include #include "kis_shape_selection.h" #include "kis_colorize_dom_utils.h" #include "kis_dom_utils.h" #include "kis_raster_keyframe_channel.h" #include "kis_paint_device_frames_interface.h" #include "kis_filter_registry.h" using namespace KRA; QString expandEncodedDirectory(const QString& _intern) { QString intern = _intern; QString result; int pos; while ((pos = intern.indexOf('/')) != -1) { if (QChar(intern.at(0)).isDigit()) result += "part"; result += intern.left(pos + 1); // copy numbers (or "pictures") + "/" intern = intern.mid(pos + 1); // remove the dir we just processed } if (!intern.isEmpty() && QChar(intern.at(0)).isDigit()) result += "part"; result += intern; return result; } KisKraLoadVisitor::KisKraLoadVisitor(KisImageSP image, KoStore *store, KoShapeControllerBase *shapeController, QMap &layerFilenames, QMap &keyframeFilenames, const QString & name, int syntaxVersion) : KisNodeVisitor() , m_image(image) , m_store(store) , m_external(false) , m_layerFilenames(layerFilenames) , m_keyframeFilenames(keyframeFilenames) , m_name(name) , m_shapeController(shapeController) { m_store->pushDirectory(); if (!m_store->enterDirectory(m_name)) { QStringList directories = m_store->directoryList(); dbgKrita << directories; if (directories.size() > 0) { dbgFile << "Could not locate the directory, maybe some encoding issue? Grab the first directory, that'll be the image one." << m_name << directories; m_name = directories.first(); } else { dbgFile << "Could not enter directory" << m_name << ", probably an old-style file with 'part' added."; m_name = expandEncodedDirectory(m_name); } } else { m_store->popDirectory(); } m_syntaxVersion = syntaxVersion; } void KisKraLoadVisitor::setExternalUri(const QString &uri) { m_external = true; m_uri = uri; } bool KisKraLoadVisitor::visit(KisExternalLayer * layer) { bool result = false; if (auto *referencesLayer = dynamic_cast(layer)) { Q_FOREACH(KoShape *shape, referencesLayer->shapes()) { auto *reference = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(reference, false); while (!reference->loadImage(m_store)) { if (reference->embed()) { m_errorMessages << i18n("Could not load embedded reference image %1 ", reference->internalFile()); break; } else { QString msg = i18nc( "@info", "A reference image linked to an external file could not be loaded.\n\n" "Path: %1\n\n" "Do you want to select another location?", reference->filename()); int locateManually = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); QString url; if (locateManually == QMessageBox::Yes) { KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); url = dialog.filename(); } if (url.isEmpty()) { break; } else { reference->setFilename(url); } } } } } else if (KisShapeLayer *shapeLayer = dynamic_cast(layer)) { if (!loadMetaData(layer)) { return false; } m_store->pushDirectory(); m_store->enterDirectory(getLocation(layer, DOT_SHAPE_LAYER)) ; result = shapeLayer->loadLayer(m_store); m_store->popDirectory(); } result = visitAll(layer) && result; return result; } bool KisKraLoadVisitor::visit(KisPaintLayer *layer) { loadNodeKeyframes(layer); if (!loadPaintDevice(layer->paintDevice(), getLocation(layer))) { return false; } if (!loadProfile(layer->paintDevice(), getLocation(layer, DOT_ICC))) { return false; } if (!loadMetaData(layer)) { return false; } if (m_syntaxVersion == 1) { // Check whether there is a file with a .mask extension in the // layer directory, if so, it's an old-style transparency mask // that should be converted. QString location = getLocation(layer, ".mask"); if (m_store->open(location)) { KisSelectionSP selection = KisSelectionSP(new KisSelection()); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); if (!pixelSelection->read(m_store->device())) { pixelSelection->disconnect(); } else { KisTransparencyMask* mask = new KisTransparencyMask(); mask->setSelection(selection); m_image->addNode(mask, layer, layer->firstChild()); } m_store->close(); } } bool result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisGroupLayer *layer) { if (*layer->colorSpace() != *m_image->colorSpace()) { layer->resetCache(m_image->colorSpace()); } if (!loadMetaData(layer)) { return false; } bool result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisAdjustmentLayer* layer) { loadNodeKeyframes(layer); // Adjustmentlayers are tricky: there's the 1.x style and the 2.x // style, which has selections with selection components bool result = true; if (m_syntaxVersion == 1) { KisSelectionSP selection = new KisSelection(); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); result = loadPaintDevice(pixelSelection, getLocation(layer, ".selection")); layer->setInternalSelection(selection); } else if (m_syntaxVersion == 2) { result = loadSelection(getLocation(layer), layer->internalSelection()); } else { // We use the default, empty selection } if (!loadMetaData(layer)) { return false; } loadFilterConfiguration(layer, getLocation(layer, DOT_FILTERCONFIG)); fixOldFilterConfigurations(layer->filter()); result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisGeneratorLayer *layer) { if (!loadMetaData(layer)) { return false; } bool result = true; loadNodeKeyframes(layer); result = loadSelection(getLocation(layer), layer->internalSelection()); // HACK ALERT: we set the same filter again to ensure the layer // is correctly updated result = loadFilterConfiguration(layer, getLocation(layer, DOT_FILTERCONFIG)); layer->setFilter(layer->filter()); result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisCloneLayer *layer) { if (!loadMetaData(layer)) { return false; } // the layer might have already been lazily initialized // from the mask loading code if (layer->copyFrom()) { return true; } KisNodeSP srcNode = layer->copyFromInfo().findNode(m_image->rootLayer()); - KisLayerSP srcLayer = qobject_cast(srcNode.data()); - Q_ASSERT(srcLayer); + if (!srcNode.isNull()) { + KisLayerSP srcLayer = qobject_cast(srcNode.data()); + Q_ASSERT(srcLayer); - layer->setCopyFrom(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()); result = loadFilterConfiguration(mask, getLocation(mask, DOT_FILTERCONFIG)); fixOldFilterConfigurations(mask->filter()); return result; } bool KisKraLoadVisitor::visit(KisTransformMask *mask) { QString location = getLocation(mask, DOT_TRANSFORMCONFIG); if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); if (!data.isEmpty()) { QDomDocument doc; doc.setContent(data); QDomElement rootElement = doc.documentElement(); QDomElement main; if (!KisDomUtils::findOnlyElement(rootElement, "main", &main/*, &m_errorMessages*/)) { return false; } QString id = main.attribute("id", "not-valid"); if (id == "not-valid") { m_errorMessages << i18n("Could not load \"id\" of the transform mask"); return false; } QDomElement data; if (!KisDomUtils::findOnlyElement(rootElement, "data", &data, &m_errorMessages)) { return false; } KisTransformMaskParamsInterfaceSP params = KisTransformMaskParamsFactoryRegistry::instance()->createParams(id, data); if (!params) { m_errorMessages << i18n("Could not create transform mask params"); return false; } mask->setTransformParams(params); loadNodeKeyframes(mask); params->clearChangedFlag(); return true; } } return false; } bool KisKraLoadVisitor::visit(KisTransparencyMask *mask) { initSelectionForMask(mask); loadNodeKeyframes(mask); return loadSelection(getLocation(mask), mask->selection()); } bool KisKraLoadVisitor::visit(KisSelectionMask *mask) { initSelectionForMask(mask); return loadSelection(getLocation(mask), mask->selection()); } bool KisKraLoadVisitor::visit(KisColorizeMask *mask) { m_store->pushDirectory(); QString location = getLocation(mask, DOT_COLORIZE_MASK); m_store->enterDirectory(location) ; QByteArray data; if (!m_store->extractFile("content.xml", data)) return false; QDomDocument doc; if (!doc.setContent(data)) return false; QVector strokes; if (!KisDomUtils::loadValue(doc.documentElement(), COLORIZE_KEYSTROKES_SECTION, &strokes, mask->colorSpace())) return false; int i = 0; Q_FOREACH (const KisLazyFillTools::KeyStroke &stroke, strokes) { const QString fileName = QString("%1_%2").arg(COLORIZE_KEYSTROKE).arg(i++); loadPaintDevice(stroke.dev, fileName); } mask->setKeyStrokesDirect(QList::fromVector(strokes)); loadPaintDevice(mask->coloringProjection(), COLORIZE_COLORING_DEVICE); mask->resetCache(); m_store->popDirectory(); return true; } QStringList KisKraLoadVisitor::errorMessages() const { return m_errorMessages; } QStringList KisKraLoadVisitor::warningMessages() const { return m_warningMessages; } struct SimpleDevicePolicy { bool read(KisPaintDeviceSP dev, QIODevice *stream) { return dev->read(stream); } void setDefaultPixel(KisPaintDeviceSP dev, const KoColor &defaultPixel) const { return dev->setDefaultPixel(defaultPixel); } }; struct FramedDevicePolicy { FramedDevicePolicy(int frameId) : m_frameId(frameId) {} bool read(KisPaintDeviceSP dev, QIODevice *stream) { return dev->framesInterface()->readFrame(stream, m_frameId); } void setDefaultPixel(KisPaintDeviceSP dev, const KoColor &defaultPixel) const { return dev->framesInterface()->setFrameDefaultPixel(defaultPixel, m_frameId); } int m_frameId; }; bool KisKraLoadVisitor::loadPaintDevice(KisPaintDeviceSP device, const QString& location) { // Layer data KisPaintDeviceFramesInterface *frameInterface = device->framesInterface(); QList frames; if (frameInterface) { frames = device->framesInterface()->frames(); } if (!frameInterface || frames.count() <= 1) { return loadPaintDeviceFrame(device, location, SimpleDevicePolicy()); } else { KisRasterKeyframeChannel *keyframeChannel = device->keyframeChannel(); for (int i = 0; i < frames.count(); i++) { int id = frames[i]; if (keyframeChannel->frameFilename(id).isEmpty()) { m_warningMessages << i18n("Could not find keyframe pixel data for frame %1 in %2.", id, location); } else { Q_ASSERT(!keyframeChannel->frameFilename(id).isEmpty()); QString frameFilename = getLocation(keyframeChannel->frameFilename(id)); Q_ASSERT(!frameFilename.isEmpty()); if (!loadPaintDeviceFrame(device, frameFilename, FramedDevicePolicy(id))) { m_warningMessages << i18n("Could not load keyframe pixel data for frame %1 in %2.", id, location); } } } } return true; } template bool KisKraLoadVisitor::loadPaintDeviceFrame(KisPaintDeviceSP device, const QString &location, DevicePolicy policy) { { const int pixelSize = device->colorSpace()->pixelSize(); KoColor color(Qt::transparent, device->colorSpace()); if (m_store->open(location + ".defaultpixel")) { if (m_store->size() == pixelSize) { m_store->read((char*)color.data(), pixelSize); } m_store->close(); } policy.setDefaultPixel(device, color); } if (m_store->open(location)) { if (!policy.read(device, m_store->device())) { m_warningMessages << i18n("Could not read pixel data: %1.", location); device->disconnect(); m_store->close(); return true; } m_store->close(); } else { m_warningMessages << i18n("Could not load pixel data: %1.", location); return true; } return true; } bool KisKraLoadVisitor::loadProfile(KisPaintDeviceSP device, const QString& location) { if (m_store->hasFile(location)) { m_store->open(location); QByteArray data; data.resize(m_store->size()); dbgFile << "Data to load: " << m_store->size() << " from " << location << " with color space " << device->colorSpace()->id(); int read = m_store->read(data.data(), m_store->size()); dbgFile << "Profile size: " << data.size() << " " << m_store->atEnd() << " " << m_store->device()->bytesAvailable() << " " << read; m_store->close(); KoHashGenerator *hashGenerator = KoHashGeneratorProvider::instance()->getGenerator("MD5"); QByteArray hash = hashGenerator->generateHash(data); if (m_profileCache.contains(hash)) { if (device->setProfile(m_profileCache[hash], 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(KisNodeFilterInterface *nodeInterface, const QString& location) { KisFilterConfigurationSP kfc = nodeInterface->filter(); if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); if (!data.isEmpty()) { QDomDocument doc; doc.setContent(data); QDomElement e = doc.documentElement(); if (e.tagName() == "filterconfig") { kfc->fromLegacyXML(e); } else { kfc->fromXML(e); } loadDeprecatedFilter(kfc); return true; } } m_warningMessages << i18n("Could not filter configuration %1.", location); return true; } void KisKraLoadVisitor::fixOldFilterConfigurations(KisFilterConfigurationSP kfc) { KisFilterSP filter = KisFilterRegistry::instance()->value(kfc->name()); KIS_SAFE_ASSERT_RECOVER_RETURN(filter); if (!filter->configurationAllowedForMask(kfc)) { filter->fixLoadedFilterConfigurationForMasks(kfc); } KIS_SAFE_ASSERT_RECOVER_NOOP(filter->configurationAllowedForMask(kfc)); } bool KisKraLoadVisitor::loadMetaData(KisNode* node) { KisLayer* layer = qobject_cast(node); if (!layer) return true; KisMetaData::IOBackend* backend = KisMetaData::IOBackendRegistry::instance()->get("xmp"); if (!backend || !backend->supportLoading()) { if (backend) dbgFile << "Backend " << backend->id() << " does not support loading."; else dbgFile << "Could not load the XMP backend at all"; return true; } QString location = getLocation(node, QString(".") + backend->id() + DOT_METADATA); dbgFile << "going to load " << backend->id() << ", " << backend->name() << " from " << location; if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); QBuffer buffer(&data); if (!backend->loadFrom(layer->metaData(), &buffer)) { m_warningMessages << i18n("Could not load metadata for layer %1.", layer->name()); } } return true; } bool KisKraLoadVisitor::loadSelection(const QString& location, KisSelectionSP dstSelection) { // by default the selection is expected to be fully transparent { KisPixelSelectionSP pixelSelection = dstSelection->pixelSelection(); KoColor transparent(Qt::transparent, pixelSelection->colorSpace()); pixelSelection->setDefaultPixel(transparent); } // Pixel selection bool result = true; QString pixelSelectionLocation = location + DOT_PIXEL_SELECTION; if (m_store->hasFile(pixelSelectionLocation)) { KisPixelSelectionSP pixelSelection = dstSelection->pixelSelection(); result = loadPaintDevice(pixelSelection, pixelSelectionLocation); if (!result) { m_warningMessages << i18n("Could not load raster selection %1.", location); } pixelSelection->invalidateOutlineCache(); } // Shape selection QString shapeSelectionLocation = location + DOT_SHAPE_SELECTION; if (m_store->hasFile(shapeSelectionLocation + "/content.svg") || m_store->hasFile(shapeSelectionLocation + "/content.xml")) { m_store->pushDirectory(); m_store->enterDirectory(shapeSelectionLocation) ; KisShapeSelection* shapeSelection = new KisShapeSelection(m_shapeController, m_image, dstSelection); dstSelection->setShapeSelection(shapeSelection); result = shapeSelection->loadSelection(m_store); m_store->popDirectory(); if (!result) { m_warningMessages << i18n("Could not load vector selection %1.", location); } } return true; } QString KisKraLoadVisitor::getLocation(KisNode* node, const QString& suffix) { return getLocation(m_layerFilenames[node], suffix); } QString KisKraLoadVisitor::getLocation(const QString &filename, const QString& suffix) { QString location = m_external ? QString() : m_uri; location += m_name + LAYER_PATH + filename + suffix; return location; } void KisKraLoadVisitor::loadNodeKeyframes(KisNode *node) { if (!m_keyframeFilenames.contains(node)) return; node->enableAnimation(); const QString &location = getLocation(m_keyframeFilenames[node]); if (!m_store->open(location)) { m_errorMessages << i18n("Could not load keyframes from %1.", location); return; } QString errorMsg; int errorLine; int errorColumn; QDomDocument dom; bool ok = dom.setContent(m_store->device(), &errorMsg, &errorLine, &errorColumn); m_store->close(); if (!ok) { m_errorMessages << i18n("parsing error in the keyframe file %1 at line %2, column %3\nError message: %4", location, errorLine, errorColumn, i18n(errorMsg.toUtf8())); return; } QDomElement root = dom.firstChildElement(); for (QDomElement child = root.firstChildElement(); !child.isNull(); child = child.nextSiblingElement()) { if (child.nodeName().toUpper() == "CHANNEL") { QString id = child.attribute("name"); KisKeyframeChannel *channel = node->getKeyframeChannel(id, true); if (!channel) { m_warningMessages << i18n("unknown keyframe channel type: %1 in %2", id, location); continue; } channel->loadXML(child); } } } void KisKraLoadVisitor::loadDeprecatedFilter(KisFilterConfigurationSP cfg) { if (cfg->getString("legacy") == "left edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "yFall"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } else if (cfg->getString("legacy") == "right edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "yGrowth"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } else if (cfg->getString("legacy") == "top edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "xGrowth"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } else if (cfg->getString("legacy") == "bottom edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "xFall"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } } diff --git a/plugins/impex/libkra/tests/CMakeLists.txt b/plugins/impex/libkra/tests/CMakeLists.txt index 096295b95c..99cf94589e 100644 --- a/plugins/impex/libkra/tests/CMakeLists.txt +++ b/plugins/impex/libkra/tests/CMakeLists.txt @@ -1,12 +1,18 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) +macro_add_unittest_definitions() macro_add_unittest_definitions() ecm_add_tests( kis_kra_loader_test.cpp - kis_kra_saver_test.cpp - LINK_LIBRARIES kritaui kritalibkra Qt5::Test NAME_PREFIX "plugins-impex-") + + +krita_add_broken_unit_test(kis_kra_saver_test.cpp + TEST_NAME kis_kra_saver_test.cpp + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "plugins-impex-") + diff --git a/plugins/impex/ora/kis_open_raster_stack_save_visitor.cpp b/plugins/impex/ora/kis_open_raster_stack_save_visitor.cpp index 9fd6fc0d07..c7b3b6ada0 100644 --- a/plugins/impex/ora/kis_open_raster_stack_save_visitor.cpp +++ b/plugins/impex/ora/kis_open_raster_stack_save_visitor.cpp @@ -1,172 +1,182 @@ /* * Copyright (c) 2006-2007,2009 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_open_raster_stack_save_visitor.h" #include #include #include #include #include "kis_adjustment_layer.h" #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "kis_group_layer.h" #include "kis_paint_layer.h" #include #include "kis_open_raster_save_context.h" #include #include struct KisOpenRasterStackSaveVisitor::Private { Private() {} KisOpenRasterSaveContext* saveContext; QDomDocument layerStack; QDomElement currentElement; vKisNodeSP activeNodes; }; KisOpenRasterStackSaveVisitor::KisOpenRasterStackSaveVisitor(KisOpenRasterSaveContext* saveContext, vKisNodeSP activeNodes) : d(new Private) { d->saveContext = saveContext; d->activeNodes = activeNodes; } KisOpenRasterStackSaveVisitor::~KisOpenRasterStackSaveVisitor() { delete d; } void KisOpenRasterStackSaveVisitor::saveLayerInfo(QDomElement& elt, KisLayer* layer) { elt.setAttribute("name", layer->name()); elt.setAttribute("opacity", QString().setNum(layer->opacity() / 255.0)); elt.setAttribute("visibility", layer->visible() ? "visible" : "hidden"); + elt.setAttribute("x", QString().setNum(layer->x())); + elt.setAttribute("y", QString().setNum(layer->y())); if (layer->userLocked()) { elt.setAttribute("edit-locked", "true"); } if (d->activeNodes.contains(layer)) { elt.setAttribute("selected", "true"); } QString compop = layer->compositeOpId(); if (layer->compositeOpId() == COMPOSITE_CLEAR) compop = "svg:clear"; - else if (layer->compositeOpId() == COMPOSITE_OVER) compop = "svg:src-over"; else if (layer->compositeOpId() == COMPOSITE_ERASE) compop = "svg:dst-out"; - else if (layer->alphaChannelDisabled()) compop = "svg:src-atop"; else if (layer->compositeOpId() == COMPOSITE_DESTINATION_ATOP) compop = "svg:dst-atop"; else if (layer->compositeOpId() == COMPOSITE_DESTINATION_IN) compop = "svg:dst-in"; else if (layer->compositeOpId() == COMPOSITE_ADD) compop = "svg:plus"; else if (layer->compositeOpId() == COMPOSITE_MULT) compop = "svg:multiply"; else if (layer->compositeOpId() == COMPOSITE_SCREEN) compop = "svg:screen"; else if (layer->compositeOpId() == COMPOSITE_OVERLAY) compop = "svg:overlay"; else if (layer->compositeOpId() == COMPOSITE_DARKEN) compop = "svg:darken"; else if (layer->compositeOpId() == COMPOSITE_LIGHTEN) compop = "svg:lighten"; else if (layer->compositeOpId() == COMPOSITE_DODGE) compop = "svg:color-dodge"; else if (layer->compositeOpId() == COMPOSITE_BURN) compop = "svg:color-burn"; else if (layer->compositeOpId() == COMPOSITE_HARD_LIGHT) compop = "svg:hard-light"; else if (layer->compositeOpId() == COMPOSITE_SOFT_LIGHT_SVG) compop = "svg:soft-light"; else if (layer->compositeOpId() == COMPOSITE_DIFF) compop = "svg:difference"; else if (layer->compositeOpId() == COMPOSITE_COLOR) compop = "svg:color"; else if (layer->compositeOpId() == COMPOSITE_LUMINIZE) compop = "svg:luminosity"; else if (layer->compositeOpId() == COMPOSITE_HUE) compop = "svg:hue"; else if (layer->compositeOpId() == COMPOSITE_SATURATION) compop = "svg:saturation"; + + // it is important that the check for alphaChannelDisabled (and other non compositeOpId checks) + // come before the check for COMPOSITE_OVER, otherwise they will be logically ignored. + else if (layer->alphaChannelDisabled()) compop = "svg:src-atop"; + else if (layer->compositeOpId() == COMPOSITE_OVER) compop = "svg:src-over"; + //else if (layer->compositeOpId() == COMPOSITE_EXCLUSION) compop = "svg:exclusion"; else compop = "krita:" + layer->compositeOpId(); elt.setAttribute("composite-op", compop); } bool KisOpenRasterStackSaveVisitor::visit(KisPaintLayer *layer) { return saveLayer(layer); } bool KisOpenRasterStackSaveVisitor::visit(KisGeneratorLayer* layer) { return saveLayer(layer); } bool KisOpenRasterStackSaveVisitor::visit(KisGroupLayer *layer) { QDomElement previousElt = d->currentElement; QDomElement elt = d->layerStack.createElement("stack"); d->currentElement = elt; saveLayerInfo(elt, layer); QString isolate = "isolate"; if (layer->passThroughMode()) { isolate = "auto"; } elt.setAttribute("isolation", isolate); visitAll(layer); if (!previousElt.isNull()) { previousElt.insertBefore(elt, QDomNode()); d->currentElement = previousElt; } else { QDomElement imageElt = d->layerStack.createElement("image"); int width = layer->image()->width(); int height = layer->image()->height(); int xRes = (int)(qRound(layer->image()->xRes() * 72)); int yRes = (int)(qRound(layer->image()->yRes() * 72)); imageElt.setAttribute("version", "0.0.1"); imageElt.setAttribute("w", width); imageElt.setAttribute("h", height); imageElt.setAttribute("xres", xRes); imageElt.setAttribute("yres", yRes); imageElt.appendChild(elt); d->layerStack.insertBefore(imageElt, QDomNode()); d->currentElement = QDomElement(); d->saveContext->saveStack(d->layerStack); } return true; } bool KisOpenRasterStackSaveVisitor::visit(KisAdjustmentLayer *layer) { QDomElement elt = d->layerStack.createElement("filter"); saveLayerInfo(elt, layer); elt.setAttribute("type", "applications:krita:" + layer->filter()->name()); return true; } bool KisOpenRasterStackSaveVisitor::visit(KisCloneLayer *layer) { return saveLayer(layer); } bool KisOpenRasterStackSaveVisitor::visit(KisExternalLayer * layer) { return saveLayer(layer); } bool KisOpenRasterStackSaveVisitor::saveLayer(KisLayer *layer) { - QString filename = d->saveContext->saveDeviceData(layer->projection(), layer->metaData(), layer->image()->bounds(), layer->image()->xRes(), layer->image()->yRes()); + + // here we adjust the bounds to encompass the entire area of the layer with color data by adding the current offsets + QRect adjustedBounds = layer->image()->bounds(); + adjustedBounds.adjust(layer->x(), layer->y(), layer->x(), layer->y()); + QString filename = d->saveContext->saveDeviceData(layer->projection(), layer->metaData(), adjustedBounds, layer->image()->xRes(), layer->image()->yRes()); QDomElement elt = d->layerStack.createElement("layer"); saveLayerInfo(elt, layer); elt.setAttribute("src", filename); d->currentElement.insertBefore(elt, QDomNode()); return true; } diff --git a/plugins/impex/ora/ora_converter.cpp b/plugins/impex/ora/ora_converter.cpp index 49667f9058..2f2f394eb0 100644 --- a/plugins/impex/ora/ora_converter.cpp +++ b/plugins/impex/ora/ora_converter.cpp @@ -1,118 +1,126 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2 of the License, 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 "ora_converter.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_png_converter.h" #include "kis_open_raster_load_context.h" #include "kis_open_raster_save_context.h" OraConverter::OraConverter(KisDocument *doc) : m_doc(doc) , m_stop(false) { } OraConverter::~OraConverter() { } KisImportExportErrorCode OraConverter::buildImage(QIODevice *io) { KoStore* store = KoStore::createStore(io, KoStore::Read, "image/openraster", KoStore::Zip); if (!store) { delete store; - return ImportExportCodes::Failure; + return ImportExportCodes::FileFormatIncorrect; } KisOpenRasterLoadContext olc(store); KisOpenRasterStackLoadVisitor orslv(m_doc->createUndoStore(), &olc); orslv.loadImage(); m_image = orslv.image(); + + qDebug() << "m_image" << m_image; + + if (!m_image) { + delete store; + return ImportExportCodes::ErrorWhileReading; + } + m_activeNodes = orslv.activeNodes(); delete store; return ImportExportCodes::OK; } KisImageSP OraConverter::image() { return m_image; } vKisNodeSP OraConverter::activeNodes() { return m_activeNodes; } KisImportExportErrorCode OraConverter::buildFile(QIODevice *io, KisImageSP image, vKisNodeSP activeNodes) { // Open file for writing KoStore* store = KoStore::createStore(io, KoStore::Write, "image/openraster", KoStore::Zip); if (!store) { delete store; return ImportExportCodes::Failure; } KisOpenRasterSaveContext osc(store); KisOpenRasterStackSaveVisitor orssv(&osc, activeNodes); image->rootLayer()->accept(orssv); if (store->open("Thumbnails/thumbnail.png")) { QSize previewSize = image->bounds().size(); previewSize.scale(QSize(256,256), Qt::KeepAspectRatio); QImage preview = image->convertToQImage(previewSize, 0); KoStoreDevice io(store); if (io.open(QIODevice::WriteOnly)) { preview.save(&io, "PNG"); } io.close(); store->close(); } KisPaintDeviceSP dev = image->projection(); KisPNGConverter::saveDeviceToStore("mergedimage.png", image->bounds(), image->xRes(), image->yRes(), dev, store); delete store; return ImportExportCodes::OK; } void OraConverter::cancel() { m_stop = true; } diff --git a/plugins/impex/ora/ora_import.cc b/plugins/impex/ora/ora_import.cc index d558a1d3de..7be26247c2 100644 --- a/plugins/impex/ora/ora_import.cc +++ b/plugins/impex/ora/ora_import.cc @@ -1,53 +1,56 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2 of the License, 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 "ora_import.h" #include #include #include #include #include "ora_converter.h" K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_ora_import.json", registerPlugin();) OraImport::OraImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } OraImport::~OraImport() { } KisImportExportErrorCode OraImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { OraConverter oraConverter(document); KisImportExportErrorCode result = oraConverter.buildImage(io); if (result.isOk()) { document->setCurrentImage(oraConverter.image()); if (oraConverter.activeNodes().size() > 0) { document->setPreActivatedNode(oraConverter.activeNodes()[0]); } } + + qDebug() << ">>>>>>>>>" << result; + return result; } #include diff --git a/plugins/impex/ora/tests/CMakeLists.txt b/plugins/impex/ora/tests/CMakeLists.txt index 40a1fd500f..9dfb9bc1fc 100644 --- a/plugins/impex/ora/tests/CMakeLists.txt +++ b/plugins/impex/ora/tests/CMakeLists.txt @@ -1,11 +1,12 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) include(KritaAddBrokenUnitTest) +macro_add_unittest_definitions() macro_add_unittest_definitions() -ecm_add_test(KisOraTest.cpp +krita_add_broken_unit_test( KisOraTest.cpp TEST_NAME KisOraTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/png/tests/CMakeLists.txt b/plugins/impex/png/tests/CMakeLists.txt index 8d78edc511..69c1557c77 100644 --- a/plugins/impex/png/tests/CMakeLists.txt +++ b/plugins/impex/png/tests/CMakeLists.txt @@ -1,11 +1,12 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() +include(KritaAddBrokenUnitTest) -ecm_add_test(kis_png_test.cpp +krita_add_broken_unit_test(kis_png_test.cpp TEST_NAME kis_png_test LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/psd/psd_additional_layer_info_block.h b/plugins/impex/psd/psd_additional_layer_info_block.h index 4dc020ac26..24cab8e122 100644 --- a/plugins/impex/psd/psd_additional_layer_info_block.h +++ b/plugins/impex/psd/psd_additional_layer_info_block.h @@ -1,295 +1,295 @@ /* * Copyright (c) 2014 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 PSD_ADDITIONAL_LAYER_INFO_BLOCK_H #define PSD_ADDITIONAL_LAYER_INFO_BLOCK_H #include #include #include #include #include #include #include #include #include #include #include "psd.h" #include "psd_header.h" // additional layer information // LEVELS // Level record struct psd_layer_level_record { quint16 input_floor; // (0...253) quint16 input_ceiling; // (2...255) quint16 output_floor; // 255). Matched to input floor. quint16 output_ceiling; // (0...255) float gamma; // Short integer from 10...999 representing 0.1...9.99. Applied to all image data. }; // Levels settings files are loaded and saved in the Levels dialog. struct psd_layer_levels { psd_layer_level_record record[29]; // 29 sets of level records, each level containing 5 qint8 integers // Photoshop CS (8.0) Additional information // At the end of the Version 2 file is the following information: quint16 extra_level_count; // Count of total level record structures. Subtract the legacy number of level record structures, 29, to determine how many are remaining in the file for reading. psd_layer_level_record *extra_record; // Additianol level records according to count quint8 lookup_table[3][256]; }; // CURVES // The following is the data for each curve specified by count above struct psd_layer_curves_data { quint16 channel_index; // Before each curve is a channel index. quint16 point_count; // Count of points in the curve (qint8 integer from 2...19) quint16 output_value[19]; // All coordinates have range 0 to 255 quint16 input_value[19]; }; // Curves file format struct psd_layer_curves { quint16 curve_count; // Count of curves in the file. psd_layer_curves_data * curve; quint8 lookup_table[3][256]; }; // BRIGHTNESS AND CONTRAST struct psd_layer_brightness_contrast { qint8 brightness; qint8 contrast; qint8 mean_value; // for brightness and contrast qint8 Lab_color; quint8 lookup_table[256]; }; // COLOR BALANCE struct psd_layer_color_balance { qint8 cyan_red[3]; // (-100...100). shadows, midtones, highlights qint8 magenta_green[3]; qint8 yellow_blue[3]; bool preserve_luminosity; quint8 lookup_table[3][256]; }; // HUE/SATURATION // Hue/Saturation settings files are loaded and saved in Photoshop¡¯s Hue/Saturation dialog struct psd_layer_hue_saturation { quint8 hue_or_colorization; // 0 = Use settings for hue-adjustment; 1 = Use settings for colorization. qint8 colorization_hue; // Photoshop 5.0: The actual values are stored for the new version. Hue is - 180...180, Saturation is 0...100, and Lightness is -100...100. qint8 colorization_saturation;// Photoshop 4.0: Three qint8 integers Hue, Saturation, and Lightness from ¨C100...100. qint8 colorization_lightness; // The user interface represents hue as ¨C180...180, saturation as 0...100, and Lightness as -100...1000, as the traditional HSB color wheel, with red = 0. qint8 master_hue; // Master hue, saturation and lightness values. qint8 master_saturation; qint8 master_lightness; qint8 range_values[6][4]; // For RGB and CMYK, those values apply to each of the six hextants in the HSB color wheel: those image pixels nearest to red, yellow, green, cyan, blue, or magenta. These numbers appear in the user interface from ¨C60...60, however the slider will reflect each of the possible 201 values from ¨C100...100. qint8 setting_values[6][3]; // For Lab, the first four of the six values are applied to image pixels in the four Lab color quadrants, yellow, green, blue, and magenta. The other two values are ignored ( = 0). The values appear in the user interface from ¨C90 to 90. quint8 lookup_table[6][360]; }; // SELECTIVE COLOR // Selective Color settings files are loaded and saved in Photoshop¡¯s Selective Color dialog. struct psd_layer_selective_color { quint16 correction_method; // 0 = Apply color correction in relative mode; 1 = Apply color correction in absolute mode. qint8 cyan_correction[10]; // Amount of cyan correction. Short integer from ¨C100...100. qint8 magenta_correction[10]; // Amount of magenta correction. Short integer from ¨C100...100. qint8 yellow_correction[10]; // Amount of yellow correction. Short integer from ¨C100...100. qint8 black_correction[10]; // Amount of black correction. Short integer from ¨C100...100. }; // THRESHOLD struct psd_layer_threshold { quint16 level; // (1...255) } ; // INVERT // no parameter // POSTERIZE struct psd_layer_posterize { quint16 levels; // (2...255) quint8 lookup_table[256]; }; // CHANNEL MIXER struct psd_layer_channel_mixer { bool monochrome; qint8 red_cyan[4]; // RGB or CMYK color plus constant for the mixer settings. 4 * 2 bytes of color with 2 bytes of constant. qint8 green_magenta[4]; // (-200...200) qint8 blue_yellow[4]; qint8 black[4]; qint8 constant[4]; }; // PHOTO FILTER struct psd_layer_photo_filter { qint32 x_color; // 4 bytes each for XYZ color qint32 y_color; qint32 z_color; qint32 density; // (1...100) bool preserve_luminosity; }; #include struct psd_layer_solid_color { quint32 id; QColor fill_color; }; struct psd_layer_gradient_fill { quint32 id; double angle; psd_gradient_style style; qint32 scale; bool reverse; // Is gradient reverse bool dithered; // Is gradient dithered bool align_with_layer; psd_gradient_color gradient_color; }; struct psd_layer_pattern_fill { quint32 id; psd_pattern_info pattern_info; qint32 scale; }; struct psd_layer_type_face { qint8 mark; // Mark value qint32 font_type; // Font type data qint8 font_name[256]; // Pascal string of font name qint8 font_family_name[256]; // Pascal string of font family name qint8 font_style_name[256]; // Pascal string of font style name qint8 script; // Script value qint32 number_axes_vector; // Number of design axes vector to follow qint32 * vector; // Design vector value }; struct psd_layer_type_style { qint8 mark; // Mark value qint8 face_mark; // Face mark value qint32 size; // Size value qint32 tracking; // Tracking value qint32 kerning; // Kerning value qint32 leading; // Leading value qint32 base_shift; // Base shift value bool auto_kern; // Auto kern on/off bool rotate; // Rotate up/down }; struct psd_layer_type_line { qint32 char_count; // Character count value qint8 orientation; // Orientation value qint8 alignment; // Alignment value qint8 actual_char; // Actual character as a double byte character qint8 style; // Style value }; struct psd_layer_type_tool { double transform_info[6]; // 6 * 8 double precision numbers for the transform information qint8 faces_count; // Count of faces psd_layer_type_face * face; qint8 styles_count; // Count of styles psd_layer_type_style * style; qint8 type; // Type value qint32 scaling_factor; // Scaling factor value qint32 character_count; // Character count value qint32 horz_place; // Horizontal placement qint32 vert_place; // Vertical placement qint32 select_start; // Select start value qint32 select_end; // Select end value qint8 lines_count; // Line count psd_layer_type_line * line; QColor color; bool anti_alias; // Anti alias on/off }; /** * @brief The PsdAdditionalLayerInfoBlock class implements the Additional Layer Information block * - * See: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_71546 + * See: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_71546 */ class PsdAdditionalLayerInfoBlock { public: PsdAdditionalLayerInfoBlock(const PSDHeader& header); typedef boost::function ExtraLayerInfoBlockHandler; void setExtraLayerInfoBlockHandler(ExtraLayerInfoBlockHandler handler); bool read(QIODevice* io); bool write(QIODevice* io, KisNodeSP node); void writeLuniBlockEx(QIODevice* io, const QString &layerName); void writeLsctBlockEx(QIODevice* io, psd_section_type sectionType, bool isPassThrough, const QString &blendModeKey); void writeLfx2BlockEx(QIODevice* io, const QDomDocument &stylesXmlDoc, bool useLfxsLayerStyleFormat); void writePattBlockEx(QIODevice* io, const QDomDocument &patternsXmlDoc); bool valid(); const PSDHeader &m_header; QString error; QStringList keys; // List of all the keys that we've seen QString unicodeLayerName; QDomDocument layerStyleXml; QVector embeddedPatterns; psd_section_type sectionDividerType; QString sectionDividerBlendMode; private: void readImpl(QIODevice* io); private: ExtraLayerInfoBlockHandler m_layerInfoBlockHandler; }; #endif // PSD_ADDITIONAL_LAYER_INFO_BLOCK_H diff --git a/plugins/impex/psd/psd_layer_section.cpp b/plugins/impex/psd/psd_layer_section.cpp index fd73f37997..ac15eaa1e8 100644 --- a/plugins/impex/psd/psd_layer_section.cpp +++ b/plugins/impex/psd/psd_layer_section.cpp @@ -1,585 +1,582 @@ /* * Copyright (c) 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. */ #include "psd_layer_section.h" #include #include #include #include #include #include #include #include #include #include "kis_dom_utils.h" #include "psd_header.h" #include "psd_utils.h" #include "compression.h" #include #include #include #include PSDLayerMaskSection::PSDLayerMaskSection(const PSDHeader& header) - : globalInfoSection(header), - m_header(header) + : globalInfoSection(header) + , m_header(header) { - hasTransparency = false; - layerMaskBlockSize = 0; - nLayers = 0; } PSDLayerMaskSection::~PSDLayerMaskSection() { qDeleteAll(layers); } bool PSDLayerMaskSection::read(QIODevice* io) { bool retval = true; // be optimistic! <:-) try { retval = readImpl(io); } catch (KisAslReaderUtils::ASLParseException &e) { warnKrita << "WARNING: PSD (emb. pattern):" << e.what(); retval = false; } return retval; } bool PSDLayerMaskSection::readLayerInfoImpl(QIODevice* io) { quint32 layerInfoSectionSize = 0; SAFE_READ_EX(io, layerInfoSectionSize); if (layerInfoSectionSize & 0x1) { warnKrita << "WARNING: layerInfoSectionSize is NOT even! Fixing..."; layerInfoSectionSize++; } { SETUP_OFFSET_VERIFIER(layerInfoSectionTag, io, layerInfoSectionSize, 0); dbgFile << "Layer info block size" << layerInfoSectionSize; if (layerInfoSectionSize > 0 ) { if (!psdread(io, &nLayers) || nLayers == 0) { error = QString("Could not read read number of layers or no layers in image. %1").arg(nLayers); return false; } hasTransparency = nLayers < 0; // first alpha channel is the alpha channel of the projection. nLayers = qAbs(nLayers); dbgFile << "Number of layers:" << nLayers; dbgFile << "Has separate projection transparency:" << hasTransparency; for (int i = 0; i < nLayers; ++i) { dbgFile << "Going to read layer" << i << "pos" << io->pos(); dbgFile << "== Enter PSDLayerRecord"; QScopedPointer layerRecord(new PSDLayerRecord(m_header)); if (!layerRecord->read(io)) { error = QString("Could not load layer %1: %2").arg(i).arg(layerRecord->error); return false; } dbgFile << "== Leave PSDLayerRecord"; dbgFile << "Finished reading layer" << i << layerRecord->layerName << "blending mode" << layerRecord->blendModeKey << io->pos() << "Number of channels:" << layerRecord->channelInfoRecords.size(); layers << layerRecord.take(); } } // get the positions for the channels belonging to each layer for (int i = 0; i < nLayers; ++i) { dbgFile << "Going to seek channel positions for layer" << i << "pos" << io->pos(); if (i > layers.size()) { error = QString("Expected layer %1, but only have %2 layers").arg(i).arg(layers.size()); return false; } PSDLayerRecord *layerRecord = layers.at(i); for (int j = 0; j < layerRecord->nChannels; ++j) { // save the current location so we can jump beyond this block later on. quint64 channelStartPos = io->pos(); dbgFile << "\tReading channel image data for channel" << j << "from pos" << io->pos(); KIS_ASSERT_RECOVER(j < layerRecord->channelInfoRecords.size()) { return false; } ChannelInfo* channelInfo = layerRecord->channelInfoRecords.at(j); quint16 compressionType; if (!psdread(io, &compressionType)) { error = "Could not read compression type for channel"; return false; } channelInfo->compressionType = (Compression::CompressionType)compressionType; dbgFile << "\t\tChannel" << j << "has compression type" << compressionType; QRect channelRect = layerRecord->channelRect(channelInfo); // read the rle row lengths; if (channelInfo->compressionType == Compression::RLE) { for(qint64 row = 0; row < channelRect.height(); ++row) { //dbgFile << "Reading the RLE bytecount position of row" << row << "at pos" << io->pos(); quint32 byteCount; if (m_header.version == 1) { quint16 _byteCount; if (!psdread(io, &_byteCount)) { error = QString("Could not read byteCount for rle-encoded channel"); return 0; } byteCount = _byteCount; } else { if (!psdread(io, &byteCount)) { error = QString("Could not read byteCount for rle-encoded channel"); return 0; } } ////dbgFile << "rle byte count" << byteCount; channelInfo->rleRowLengths << byteCount; } } // we're beyond all the length bytes, rle bytes and whatever, this is the // location of the real pixel data channelInfo->channelDataStart = io->pos(); dbgFile << "\t\tstart" << channelStartPos << "data start" << channelInfo->channelDataStart << "data length" << channelInfo->channelDataLength << "pos" << io->pos(); // make sure we are at the start of the next channel data block io->seek(channelStartPos + channelInfo->channelDataLength); // this is the length of the actual channel data bytes channelInfo->channelDataLength = channelInfo->channelDataLength - (channelInfo->channelDataStart - channelStartPos); dbgFile << "\t\tchannel record" << j << "for layer" << i << "with id" << channelInfo->channelId << "starting position" << channelInfo->channelDataStart << "with length" << channelInfo->channelDataLength << "and has compression type" << channelInfo->compressionType; } } } return true; } bool PSDLayerMaskSection::readImpl(QIODevice* io) { dbgFile << "reading layer section. Pos:" << io->pos() << "bytes left:" << io->bytesAvailable(); layerMaskBlockSize = 0; if (m_header.version == 1) { quint32 _layerMaskBlockSize = 0; if (!psdread(io, &_layerMaskBlockSize) || _layerMaskBlockSize > (quint64)io->bytesAvailable()) { error = QString("Could not read layer + mask block size. Got %1. Bytes left %2") .arg(_layerMaskBlockSize).arg(io->bytesAvailable()); return false; } layerMaskBlockSize = _layerMaskBlockSize; } else if (m_header.version == 2) { if (!psdread(io, &layerMaskBlockSize) || layerMaskBlockSize > (quint64)io->bytesAvailable()) { error = QString("Could not read layer + mask block size. Got %1. Bytes left %2") .arg(layerMaskBlockSize).arg(io->bytesAvailable()); return false; } } quint64 start = io->pos(); dbgFile << "layer + mask section size" << layerMaskBlockSize; if (layerMaskBlockSize == 0) { dbgFile << "No layer + mask info, so no layers, only a background layer"; return true; } if (!readLayerInfoImpl(io)) { return false; } quint32 globalMaskBlockLength; if (!psdread(io, &globalMaskBlockLength)) { error = "Could not read global mask info block"; return false; } if (globalMaskBlockLength > 0) { if (!psdread(io, &globalLayerMaskInfo.overlayColorSpace)) { error = "Could not read global mask info overlay colorspace"; return false; } for (int i = 0; i < 4; ++i) { if (!psdread(io, &globalLayerMaskInfo.colorComponents[i])) { error = QString("Could not read mask info visualizaion color component %1").arg(i); return false; } } if (!psdread(io, &globalLayerMaskInfo.opacity)) { error = "Could not read global mask info visualization opacity"; return false; } if (!psdread(io, &globalLayerMaskInfo.kind)) { error = "Could not read global mask info visualization type"; return false; } } // global additional sections /** * Newer versions of PSD have layers info block wrapped into * 'Lr16' or 'Lr32' additional section, while the main block is * absent. * * Here we pass the callback which should be used when such * additional section is recognized. */ globalInfoSection.setExtraLayerInfoBlockHandler(std::bind(&PSDLayerMaskSection::readLayerInfoImpl, this, std::placeholders::_1)); globalInfoSection.read(io); /* put us after this section so reading the next section will work even if we mess up */ io->seek(start + layerMaskBlockSize); return true; } struct FlattenedNode { FlattenedNode() : type(RASTER_LAYER) {} KisNodeSP node; enum Type { RASTER_LAYER, FOLDER_OPEN, FOLDER_CLOSED, SECTION_DIVIDER }; Type type; }; void addBackgroundIfNeeded(KisNodeSP root, QList &nodes) { KisGroupLayer *group = dynamic_cast(root.data()); if (!group) return; KoColor projectionColor = group->defaultProjectionColor(); if (projectionColor.opacityU8() == OPACITY_TRANSPARENT_U8) return; KisPaintLayerSP layer = new KisPaintLayer(group->image(), i18nc("Automatically created layer name when saving into PSD", "Background"), OPACITY_OPAQUE_U8); layer->paintDevice()->setDefaultPixel(projectionColor); { FlattenedNode item; item.node = layer; item.type = FlattenedNode::RASTER_LAYER; nodes << item; } } void flattenNodes(KisNodeSP node, QList &nodes) { KisNodeSP child = node->firstChild(); while (child) { const bool isLayer = child->inherits("KisLayer"); const bool isGroupLayer = child->inherits("KisGroupLayer"); if (isGroupLayer) { { FlattenedNode item; item.node = child; item.type = FlattenedNode::SECTION_DIVIDER; nodes << item; } flattenNodes(child, nodes); { FlattenedNode item; item.node = child; item.type = FlattenedNode::FOLDER_OPEN; nodes << item; } } else if (isLayer) { FlattenedNode item; item.node = child; item.type = FlattenedNode::RASTER_LAYER; nodes << item; } child = child->nextSibling(); } } KisNodeSP findOnlyTransparencyMask(KisNodeSP node, FlattenedNode::Type type) { if (type != FlattenedNode::FOLDER_OPEN && type != FlattenedNode::FOLDER_CLOSED && type != FlattenedNode::RASTER_LAYER) { return 0; } KisLayer *layer = qobject_cast(node.data()); QList masks = layer->effectMasks(); if (masks.size() != 1) return 0; KisEffectMaskSP onlyMask = masks.first(); return onlyMask->inherits("KisTransparencyMask") ? onlyMask : 0; } QDomDocument fetchLayerStyleXmlData(KisNodeSP node) { const KisLayer *layer = qobject_cast(node.data()); KisPSDLayerStyleSP layerStyle = layer->layerStyle(); if (!layerStyle) return QDomDocument(); KisAslLayerStyleSerializer serializer; serializer.setStyles(QVector() << layerStyle); return serializer.formPsdXmlDocument(); } inline QDomNode findNodeByKey(const QString &key, QDomNode parent) { return KisDomUtils::findElementByAttibute(parent, "node", "key", key); } void mergePatternsXMLSection(const QDomDocument &src, QDomDocument &dst) { QDomNode srcPatternsNode = findNodeByKey("Patterns", src.documentElement()); QDomNode dstPatternsNode = findNodeByKey("Patterns", dst.documentElement()); if (srcPatternsNode.isNull()) return; if (dstPatternsNode.isNull()) { dst = src; return; } KIS_ASSERT_RECOVER_RETURN(!srcPatternsNode.isNull()); KIS_ASSERT_RECOVER_RETURN(!dstPatternsNode.isNull()); QDomNode node = srcPatternsNode.firstChild(); while(!node.isNull()) { QDomNode importedNode = dst.importNode(node, true); KIS_ASSERT_RECOVER_RETURN(!importedNode.isNull()); dstPatternsNode.appendChild(importedNode); node = node.nextSibling(); } } bool PSDLayerMaskSection::write(QIODevice* io, KisNodeSP rootLayer) { bool retval = true; try { writeImpl(io, rootLayer); } catch (KisAslWriterUtils::ASLWriteException &e) { error = PREPEND_METHOD(e.what()); retval = false; } return retval; } void PSDLayerMaskSection::writeImpl(QIODevice* io, KisNodeSP rootLayer) { dbgFile << "Writing layer layer section"; // Build the whole layer structure QList nodes; addBackgroundIfNeeded(rootLayer, nodes); flattenNodes(rootLayer, nodes); if (nodes.isEmpty()) { throw KisAslWriterUtils::ASLWriteException("Could not find paint layers to save"); } { KisAslWriterUtils::OffsetStreamPusher layerAndMaskSectionSizeTag(io, 2); QDomDocument mergedPatternsXmlDoc; { KisAslWriterUtils::OffsetStreamPusher layerInfoSizeTag(io, 4); { // number of layers (negative, because krita always has alpha) const qint16 layersSize = -nodes.size(); SAFE_WRITE_EX(io, layersSize); dbgFile << "Number of layers" << layersSize << "at" << io->pos(); } // Layer records section Q_FOREACH (const FlattenedNode &item, nodes) { KisNodeSP node = item.node; PSDLayerRecord *layerRecord = new PSDLayerRecord(m_header); layers.append(layerRecord); KisNodeSP onlyTransparencyMask = findOnlyTransparencyMask(node, item.type); const QRect maskRect = onlyTransparencyMask ? onlyTransparencyMask->paintDevice()->exactBounds() : QRect(); const bool nodeVisible = node->visible(); const KoColorSpace *colorSpace = node->colorSpace(); const quint8 nodeOpacity = node->opacity(); const quint8 nodeClipping = 0; const KisPaintLayer *paintLayer = qobject_cast(node.data()); const bool alphaLocked = (paintLayer && paintLayer->alphaLocked()); const QString nodeCompositeOp = node->compositeOpId(); const KisGroupLayer *groupLayer = qobject_cast(node.data()); const bool nodeIsPassThrough = groupLayer && groupLayer->passThroughMode(); QDomDocument stylesXmlDoc = fetchLayerStyleXmlData(node); if (mergedPatternsXmlDoc.isNull() && !stylesXmlDoc.isNull()) { mergedPatternsXmlDoc = stylesXmlDoc; } else if (!mergedPatternsXmlDoc.isNull() && !stylesXmlDoc.isNull()) { mergePatternsXMLSection(stylesXmlDoc, mergedPatternsXmlDoc); } bool nodeIrrelevant = false; QString nodeName; KisPaintDeviceSP layerContentDevice; psd_section_type sectionType; if (item.type == FlattenedNode::RASTER_LAYER) { nodeIrrelevant = false; nodeName = node->name(); layerContentDevice = onlyTransparencyMask ? node->original() : node->projection(); sectionType = psd_other; } else { nodeIrrelevant = true; nodeName = item.type == FlattenedNode::SECTION_DIVIDER ? QString("") : node->name(); layerContentDevice = 0; sectionType = item.type == FlattenedNode::SECTION_DIVIDER ? psd_bounding_divider : item.type == FlattenedNode::FOLDER_OPEN ? psd_open_folder : psd_closed_folder; } // === no access to node anymore QRect layerRect; if (layerContentDevice) { QRect rc = layerContentDevice->exactBounds(); rc = rc.normalized(); // keep to the max of photoshop's capabilities if (rc.width() > 30000) rc.setWidth(30000); if (rc.height() > 30000) rc.setHeight(30000); layerRect = rc; } layerRecord->top = layerRect.y(); layerRecord->left = layerRect.x(); layerRecord->bottom = layerRect.y() + layerRect.height(); layerRecord->right = layerRect.x() + layerRect.width(); // colors + alpha channel // note: transparency mask not included layerRecord->nChannels = colorSpace->colorChannelCount() + 1; ChannelInfo *info = new ChannelInfo; info->channelId = -1; // For the alpha channel, which we always have in Krita, and should be saved first in layerRecord->channelInfoRecords << info; // the rest is in display order: rgb, cmyk, lab... for (int i = 0; i < (int)colorSpace->colorChannelCount(); ++i) { info = new ChannelInfo; info->channelId = i; // 0 for red, 1 = green, etc layerRecord->channelInfoRecords << info; } layerRecord->blendModeKey = composite_op_to_psd_blendmode(nodeCompositeOp); layerRecord->isPassThrough = nodeIsPassThrough; layerRecord->opacity = nodeOpacity; layerRecord->clipping = nodeClipping; layerRecord->transparencyProtected = alphaLocked; layerRecord->visible = nodeVisible; layerRecord->irrelevant = nodeIrrelevant; layerRecord->layerName = nodeName.isEmpty() ? i18n("Unnamed Layer") : nodeName; layerRecord->write(io, layerContentDevice, onlyTransparencyMask, maskRect, sectionType, stylesXmlDoc, node->inherits("KisGroupLayer")); } dbgFile << "start writing layer pixel data" << io->pos(); // Now save the pixel data Q_FOREACH (PSDLayerRecord *layerRecord, layers) { layerRecord->writePixelData(io); } } { // write the global layer mask info -- which is empty const quint32 globalMaskSize = 0; SAFE_WRITE_EX(io, globalMaskSize); } globalInfoSection.writePattBlockEx(io, mergedPatternsXmlDoc); } } diff --git a/plugins/impex/psd/psd_layer_section.h b/plugins/impex/psd/psd_layer_section.h index 55db74f209..bcdb430fec 100644 --- a/plugins/impex/psd/psd_layer_section.h +++ b/plugins/impex/psd/psd_layer_section.h @@ -1,72 +1,72 @@ /* * Copyright (c) 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 PSD_LAYER_SECTION_H #define PSD_LAYER_SECTION_H #include class QIODevice; #include #include "psd_header.h" #include "psd_layer_record.h" class PSDLayerMaskSection { public: PSDLayerMaskSection(const PSDHeader& header); ~PSDLayerMaskSection(); bool read(QIODevice* io); bool write(QIODevice* io, KisNodeSP rootLayer); QString error; - // http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_21849 - quint64 layerMaskBlockSize; // Length of the layer and mask information section + // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_21849 + quint64 layerMaskBlockSize {0}; // Length of the layer and mask information section - // layer info: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_16000 - bool hasTransparency; + // layer info: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_16000 + bool hasTransparency {false}; - qint16 nLayers; // If layer count is a negative number, its absolute value is the number of layers and the first alpha channel contains the transparency data for the merged result. + qint16 nLayers {0}; // If layer count is a negative number, its absolute value is the number of layers and the first alpha channel contains the transparency data for the merged result. QVector layers; - // mask info: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_17115 + // mask info: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_17115 struct GlobalLayerMaskInfo { - quint16 overlayColorSpace; // Overlay color space (undocumented). - quint16 colorComponents[4]; // 4 * 2 byte color components - quint16 opacity; // Opacity. 0 = transparent, 100 = opaque. - quint8 kind; // Kind. 0 = Color selected--i.e. inverted; 1 = Color protected;128 = use value stored per layer. This value is preferred. The others are for backward compatibility with beta versions. + quint16 overlayColorSpace {0}; // Overlay color space (undocumented). + quint16 colorComponents[4] {0, 0, 0, 0}; // 4 * 2 byte color components + quint16 opacity {0}; // Opacity. 0 = transparent, 100 = opaque. + quint8 kind {0}; // Kind. 0 = Color selected--i.e. inverted; 1 = Color protected;128 = use value stored per layer. This value is preferred. The others are for backward compatibility with beta versions. }; GlobalLayerMaskInfo globalLayerMaskInfo; PsdAdditionalLayerInfoBlock globalInfoSection; private: bool readLayerInfoImpl(QIODevice* io); bool readImpl(QIODevice* io); void writeImpl(QIODevice* io, KisNodeSP rootLayer); private: const PSDHeader m_header; }; #endif // PSD_LAYER_SECTION_H diff --git a/plugins/impex/psd/psd_loader.cpp b/plugins/impex/psd/psd_loader.cpp index f68e47a503..d538d215c7 100644 --- a/plugins/impex/psd/psd_loader.cpp +++ b/plugins/impex/psd/psd_loader.cpp @@ -1,380 +1,380 @@ /* * Copyright (c) 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. */ #include "psd_loader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisResourceServerProvider.h" #include "psd.h" #include "psd_header.h" #include "psd_colormode_block.h" #include "psd_utils.h" #include "psd_resource_section.h" #include "psd_layer_section.h" #include "psd_resource_block.h" #include "psd_image_data.h" PSDLoader::PSDLoader(KisDocument *doc) : m_image(0) , m_doc(doc) , m_stop(false) { } PSDLoader::~PSDLoader() { } KisImportExportErrorCode PSDLoader::decode(QIODevice *io) { // open the file dbgFile << "pos:" << io->pos(); PSDHeader header; if (!header.read(io)) { dbgFile << "failed reading header: " << header.error; return ImportExportCodes::FileFormatIncorrect; } dbgFile << header; dbgFile << "Read header. pos:" << io->pos(); PSDColorModeBlock colorModeBlock(header.colormode); if (!colorModeBlock.read(io)) { dbgFile << "failed reading colormode block: " << colorModeBlock.error; return ImportExportCodes::FileFormatIncorrect; } dbgFile << "Read color mode block. pos:" << io->pos(); PSDImageResourceSection resourceSection; if (!resourceSection.read(io)) { dbgFile << "failed image reading resource section: " << resourceSection.error; return ImportExportCodes::FileFormatIncorrect; } dbgFile << "Read image resource section. pos:" << io->pos(); PSDLayerMaskSection layerSection(header); if (!layerSection.read(io)) { dbgFile << "failed reading layer/mask section: " << layerSection.error; return ImportExportCodes::FileFormatIncorrect; } dbgFile << "Read layer/mask section. " << layerSection.nLayers << "layers. pos:" << io->pos(); // Done reading, except possibly for the image data block, which is only relevant if there // are no layers. // Get the right colorspace QPair colorSpaceId = psd_colormode_to_colormodelid(header.colormode, header.channelDepth); if (colorSpaceId.first.isNull()) { dbgFile << "Unsupported colorspace" << header.colormode << header.channelDepth; return ImportExportCodes::FormatColorSpaceUnsupported; } // Get the icc profile from the image resource section const KoColorProfile* profile = 0; if (resourceSection.resources.contains(PSDImageResourceSection::ICC_PROFILE)) { ICC_PROFILE_1039 *iccProfileData = dynamic_cast(resourceSection.resources[PSDImageResourceSection::ICC_PROFILE]->resource); if (iccProfileData ) { profile = KoColorSpaceRegistry::instance()->createColorProfile(colorSpaceId.first, colorSpaceId.second, iccProfileData->icc); dbgFile << "Loaded ICC profile" << profile->name(); delete resourceSection.resources.take(PSDImageResourceSection::ICC_PROFILE); } } // Create the colorspace const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceId.first, colorSpaceId.second, profile); if (!cs) { return ImportExportCodes::FormatColorSpaceUnsupported; } // Creating the KisImage QFile *file = dynamic_cast(io); QString name = file ? file->fileName() : "Imported"; m_image = new KisImage(m_doc->createUndoStore(), header.width, header.height, cs, name); Q_CHECK_PTR(m_image); m_image->lock(); // set the correct resolution if (resourceSection.resources.contains(PSDImageResourceSection::RESN_INFO)) { RESN_INFO_1005 *resInfo = dynamic_cast(resourceSection.resources[PSDImageResourceSection::RESN_INFO]->resource); if (resInfo) { // check resolution size is not zero if (resInfo->hRes * resInfo->vRes > 0) m_image->setResolution(POINT_TO_INCH(resInfo->hRes), POINT_TO_INCH(resInfo->vRes)); // let's skip the unit for now; we can only set that on the KisDocument, and krita doesn't use it. delete resourceSection.resources.take(PSDImageResourceSection::RESN_INFO); } } // Preserve all the annotations Q_FOREACH (PSDResourceBlock *resourceBlock, resourceSection.resources.values()) { m_image->addAnnotation(resourceBlock); } // Preserve the duotone colormode block for saving back to psd if (header.colormode == DuoTone) { KisAnnotationSP annotation = new KisAnnotation("DuotoneColormodeBlock", i18n("Duotone Colormode Block"), colorModeBlock.data); m_image->addAnnotation(annotation); } // Read the projection into our single layer. Since we only read the projection when // we have just one layer, we don't need to later on apply the alpha channel of the // first layer to the projection if the number of layers is negative/ - // See http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_16000. + // See https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_16000. if (layerSection.nLayers == 0) { dbgFile << "Position" << io->pos() << "Going to read the projection into the first layer, which Photoshop calls 'Background'"; KisPaintLayerSP layer = new KisPaintLayer(m_image, i18n("Background"), OPACITY_OPAQUE_U8); PSDImageData imageData(&header); imageData.read(io, layer->paintDevice()); m_image->addNode(layer, m_image->rootLayer()); // Only one layer, the background layer, so we're done. m_image->unlock(); return ImportExportCodes::OK; } // More than one layer, so now construct the Krita image from the info we read. QStack groupStack; groupStack.push(m_image->rootLayer()); /** * PSD has a weird "optimization": if a group layer has only one * child layer, it omits it's 'psd_bounding_divider' section. So * fi you ever see an unbalanced layers group in PSD, most * probably, it is just a single layered group. */ KisNodeSP lastAddedLayer; typedef QPair LayerStyleMapping; QVector allStylesXml; // read the channels for the various layers for(int i = 0; i < layerSection.nLayers; ++i) { PSDLayerRecord* layerRecord = layerSection.layers.at(i); dbgFile << "Going to read channels for layer" << i << layerRecord->layerName; KisLayerSP newLayer; if (layerRecord->infoBlocks.keys.contains("lsct") && layerRecord->infoBlocks.sectionDividerType != psd_other) { if (layerRecord->infoBlocks.sectionDividerType == psd_bounding_divider && !groupStack.isEmpty()) { KisGroupLayerSP groupLayer = new KisGroupLayer(m_image, "temp", OPACITY_OPAQUE_U8); m_image->addNode(groupLayer, groupStack.top()); groupStack.push(groupLayer); newLayer = groupLayer; } else if ((layerRecord->infoBlocks.sectionDividerType == psd_open_folder || layerRecord->infoBlocks.sectionDividerType == psd_closed_folder) && (groupStack.size() > 1 || (lastAddedLayer && !groupStack.isEmpty()))) { KisGroupLayerSP groupLayer; if (groupStack.size() <= 1) { groupLayer = new KisGroupLayer(m_image, "temp", OPACITY_OPAQUE_U8); m_image->addNode(groupLayer, groupStack.top()); m_image->moveNode(lastAddedLayer, groupLayer, KisNodeSP()); } else { groupLayer = groupStack.pop(); } const QDomDocument &styleXml = layerRecord->infoBlocks.layerStyleXml; if (!styleXml.isNull()) { allStylesXml << LayerStyleMapping(styleXml, groupLayer); } groupLayer->setName(layerRecord->layerName); groupLayer->setVisible(layerRecord->visible); QString compositeOp = psd_blendmode_to_composite_op(layerRecord->infoBlocks.sectionDividerBlendMode); // Krita doesn't support pass-through blend // mode. Instead it is just a property of a group // layer, so flip it if (compositeOp == COMPOSITE_PASS_THROUGH) { compositeOp = COMPOSITE_OVER; groupLayer->setPassThroughMode(true); } groupLayer->setCompositeOpId(compositeOp); newLayer = groupLayer; } else { /** * In some files saved by PS CS6 the group layer sections seem * to be unbalanced. I don't know why it happens because the * reporter didn't provide us an example file. So here we just * check if the new layer was created, and if not, skip the * initialization of masks. * * See bug: 357559 */ warnKrita << "WARNING: Provided PSD has unbalanced group " << "layer markers. Some masks and/or layers can " << "be lost while loading this file. Please " << "report a bug to Krita developers and attach " << "this file to the bugreport\n" << " " << ppVar(layerRecord->layerName) << "\n" << " " << ppVar(layerRecord->infoBlocks.sectionDividerType) << "\n" << " " << ppVar(groupStack.size()); continue; } } else { KisPaintLayerSP layer = new KisPaintLayer(m_image, layerRecord->layerName, layerRecord->opacity); layer->setCompositeOpId(psd_blendmode_to_composite_op(layerRecord->blendModeKey)); const QDomDocument &styleXml = layerRecord->infoBlocks.layerStyleXml; if (!styleXml.isNull()) { allStylesXml << LayerStyleMapping(styleXml, layer); } if (!layerRecord->readPixelData(io, layer->paintDevice())) { dbgFile << "failed reading channels for layer: " << layerRecord->layerName << layerRecord->error; return ImportExportCodes::FileFormatIncorrect; } if (!groupStack.isEmpty()) { m_image->addNode(layer, groupStack.top()); } else { m_image->addNode(layer, m_image->root()); } layer->setVisible(layerRecord->visible); newLayer = layer; } Q_FOREACH (ChannelInfo *channelInfo, layerRecord->channelInfoRecords) { if (channelInfo->channelId < -1) { KisTransparencyMaskSP mask = new KisTransparencyMask(); mask->setName(i18n("Transparency Mask")); mask->initSelection(newLayer); if (!layerRecord->readMask(io, mask->paintDevice(), channelInfo)) { dbgFile << "failed reading masks for layer: " << layerRecord->layerName << layerRecord->error; } m_image->addNode(mask, newLayer); } } lastAddedLayer = newLayer; } const QVector &embeddedPatterns = layerSection.globalInfoSection.embeddedPatterns; KisAslLayerStyleSerializer serializer; if (!embeddedPatterns.isEmpty()) { Q_FOREACH (const QDomDocument &doc, embeddedPatterns) { serializer.registerPSDPattern(doc); } } QVector allStylesForServer; if (!allStylesXml.isEmpty()) { Q_FOREACH (const LayerStyleMapping &mapping, allStylesXml) { serializer.readFromPSDXML(mapping.first); if (serializer.styles().size() == 1) { KisPSDLayerStyleSP layerStyle = serializer.styles().first(); KisLayerSP layer = mapping.second; layerStyle->setName(layer->name()); allStylesForServer << layerStyle; layer->setLayerStyle(layerStyle->clone()); } else { warnKrita << "WARNING: Couldn't read layer style!" << ppVar(serializer.styles()); } } } if (!allStylesForServer.isEmpty()) { KisPSDLayerStyleCollectionResource *collection = new KisPSDLayerStyleCollectionResource("Embedded PSD Styles.asl"); collection->setName(i18nc("Auto-generated layer style collection name for embedded styles (collection)", "<%1> (embedded)", m_image->objectName())); KIS_SAFE_ASSERT_RECOVER_NOOP(!collection->valid()); collection->setLayerStyles(allStylesForServer); KIS_SAFE_ASSERT_RECOVER_NOOP(collection->valid()); KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); server->addResource(collection, false); } m_image->unlock(); return ImportExportCodes::OK; } KisImportExportErrorCode PSDLoader::buildImage(QIODevice *io) { return decode(io); } KisImageSP PSDLoader::image() { return m_image; } void PSDLoader::cancel() { m_stop = true; } diff --git a/plugins/impex/raw/3rdparty/libkdcraw/AUTHORS b/plugins/impex/raw/3rdparty/libkdcraw/AUTHORS index 4ece2f5b77..0b6b91fa4a 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/AUTHORS +++ b/plugins/impex/raw/3rdparty/libkdcraw/AUTHORS @@ -1,15 +1,15 @@ AUTHORS AND MAINTAINERS : Caulier Gilles Marcel Wiesweg CONTRIBUTORS: Angelo Naselli Gerhard Kulzer Achim Bohnet Guillaume Castagnino THANKS: -Alex Tutubalin from LibRaw project (http://www.libraw.org) \ No newline at end of file +Alex Tutubalin from LibRaw project (https://www.libraw.org) diff --git a/plugins/impex/raw/3rdparty/libkdcraw/README b/plugins/impex/raw/3rdparty/libkdcraw/README index d85be7c116..47462fd8e6 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/README +++ b/plugins/impex/raw/3rdparty/libkdcraw/README @@ -1,61 +1,61 @@ LibRaw C++ interface for KDE -This library is a part of digiKam project (http://www.digikam.org) +This library is a part of digiKam project (https://www.digikam.org) -- AUTHORS ----------------------------------------------------------- See AUTHORS file for details. -- ABOUT ------------------------------------------------------------- Libkdcraw is a C++ interface around LibRaw library used to decode RAW -picture files. More information about LibRaw can be found at http://www.libraw.org. +picture files. More information about LibRaw can be found at https://www.libraw.org. This library is used by kipi-plugins, digiKam and others kipi host programs. The library documentation is available on header files. -- DEPENDENCIES ------------------------------------------------------- -CMake >= 2.8.12 http://www.cmake.org +CMake >= 2.8.12 https://www.cmake.org ECM >= 1.1.0 https://projects.kde.org/projects/kdesupport/extra-cmake-modules -libqt >= 5.2.0 http://qt-project.org -libkde >= 5.0.0 http://www.kde.org -libraw >= 0.16.x http://www.libraw.org +libqt >= 5.2.0 https://qt.io +libkde >= 5.0.0 https://www.kde.org +libraw >= 0.16.x https://www.libraw.org Note: all library dependencies require development and binary packages installed on your computer to compile digiKam. -- INSTALL ------------------------------------------------------------ Usual CMake options: -DCMAKE_INSTALL_PREFIX : decide where the program will be install on your computer. -DCMAKE_BUILD_TYPE : decide which type of build you want. You can chose between "debug", "profile", "relwithdebinfo" and "release". The default is "relwithdebinfo" (-O2 -g). Note: To know KDE install path on your computer, use 'kf5-config --prefix' command line like this (with debug object enabled): "cmake . -DCMAKE_BUILD_TYPE=debug -DCMAKE_INSTALL_PREFIX=`kf5-config --prefix`" -- CONTACT ------------------------------------------------------------ If you have questions, comments, suggestions to make do email at : digikam-devel@kde.org IRC channel from freenode.net server: #digikam -- BUGS --------------------------------------------------------------- IMPORTANT : the bugreports and the wishlist are hosted by the KDE bugs report system who can be contacted by the standard Kde help menu of the plugins dialog. A mail will be automatically sent to the digiKam devel mailing list. There is no need to contact directly the digiKam mailing list for a bug report or a devel wish. The current digiKam bugs and devel wish reported to the Kde bugs report can be seen at this url: -http://bugs.kde.org/buglist.cgi?product=digikam&component=libkdcraw&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED +https://bugs.kde.org/buglist.cgi?product=digikam&component=libkdcraw&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED diff --git a/plugins/impex/raw/3rdparty/libkdcraw/cmake/modules/FindLibRaw.cmake b/plugins/impex/raw/3rdparty/libkdcraw/cmake/modules/FindLibRaw.cmake index d6966dbc04..0baddc03cd 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/cmake/modules/FindLibRaw.cmake +++ b/plugins/impex/raw/3rdparty/libkdcraw/cmake/modules/FindLibRaw.cmake @@ -1,79 +1,79 @@ # - Find LibRaw -# Find the LibRaw library +# Find the LibRaw library # This module defines # LibRaw_VERSION_STRING, the version string of LibRaw # LibRaw_INCLUDE_DIR, where to find libraw.h # LibRaw_LIBRARIES, the libraries needed to use LibRaw (non-thread-safe) # LibRaw_r_LIBRARIES, the libraries needed to use LibRaw (thread-safe) # LibRaw_DEFINITIONS, the definitions needed to use LibRaw (non-thread-safe) # LibRaw_r_DEFINITIONS, the definitions needed to use LibRaw (thread-safe) # # Copyright (c) 2013, Pino Toscano # Copyright (c) 2013, Gilles Caulier # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. FIND_PACKAGE(PkgConfig) IF(PKG_CONFIG_FOUND) PKG_CHECK_MODULES(PC_LIBRAW libraw) SET(LibRaw_DEFINITIONS ${PC_LIBRAW_CFLAGS_OTHER}) PKG_CHECK_MODULES(PC_LIBRAW_R libraw_r) SET(LibRaw_r_DEFINITIONS ${PC_LIBRAW_R_CFLAGS_OTHER}) ENDIF() FIND_PATH(LibRaw_INCLUDE_DIR libraw.h HINTS ${PC_LIBRAW_INCLUDEDIR} ${PC_LibRaw_INCLUDE_DIRS} PATH_SUFFIXES libraw ) FIND_LIBRARY(LibRaw_LIBRARIES NAMES raw HINTS ${PC_LIBRAW_LIBDIR} ${PC_LIBRAW_LIBRARY_DIRS} ) FIND_LIBRARY(LibRaw_r_LIBRARIES NAMES raw_r HINTS ${PC_LIBRAW_R_LIBDIR} ${PC_LIBRAW_R_LIBRARY_DIRS} ) IF(LibRaw_INCLUDE_DIR) FILE(READ ${LibRaw_INCLUDE_DIR}/libraw_version.h _libraw_version_content) STRING(REGEX MATCH "#define LIBRAW_MAJOR_VERSION[ \t]*([0-9]*)\n" _version_major_match ${_libraw_version_content}) SET(_libraw_version_major "${CMAKE_MATCH_1}") STRING(REGEX MATCH "#define LIBRAW_MINOR_VERSION[ \t]*([0-9]*)\n" _version_minor_match ${_libraw_version_content}) SET(_libraw_version_minor "${CMAKE_MATCH_1}") STRING(REGEX MATCH "#define LIBRAW_PATCH_VERSION[ \t]*([0-9]*)\n" _version_patch_match ${_libraw_version_content}) SET(_libraw_version_patch "${CMAKE_MATCH_1}") IF(_version_major_match AND _version_minor_match AND _version_patch_match) SET(LibRaw_VERSION_STRING "${_libraw_version_major}.${_libraw_version_minor}.${_libraw_version_patch}") ELSE() IF(NOT LibRaw_FIND_QUIETLY) MESSAGE(STATUS "Failed to get version information from ${LibRaw_INCLUDE_DIR}/libraw_version.h") ENDIF() ENDIF() ENDIF() INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibRaw REQUIRED_VARS LibRaw_LIBRARIES LibRaw_INCLUDE_DIR VERSION_VAR LibRaw_VERSION_STRING ) MARK_AS_ADVANCED(LibRaw_VERSION_STRING LibRaw_INCLUDE_DIR LibRaw_LIBRARIES LibRaw_r_LIBRARIES LibRaw_DEFINITIONS LibRaw_r_DEFINITIONS ) diff --git a/plugins/impex/raw/3rdparty/libkdcraw/cmake/templates/libkdcraw.pc.cmake b/plugins/impex/raw/3rdparty/libkdcraw/cmake/templates/libkdcraw.pc.cmake index 3e4cb29116..30e2e85cec 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/cmake/templates/libkdcraw.pc.cmake +++ b/plugins/impex/raw/3rdparty/libkdcraw/cmake/templates/libkdcraw.pc.cmake @@ -1,12 +1,12 @@ prefix=${CMAKE_INSTALL_PREFIX} exec_prefix=${BIN_INSTALL_DIR} libdir=${LIB_INSTALL_DIR} includedir=${INCLUDE_INSTALL_DIR} Name: ${PROJECT_NAME} Description: A C++ wrapper around LibRaw library to decode RAW pictures. This library is used by digiKam and kipi-plugins. -URL: http://www.digikam.org/sharedlibs +URL: https://commits.kde.org/libkdcraw Requires: Version: ${DCRAW_LIB_VERSION_STRING} Libs: -L${LIB_INSTALL_DIR} -lkdcraw Cflags: -I${INCLUDE_INSTALL_DIR} diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/Mainpage.dox b/plugins/impex/raw/3rdparty/libkdcraw/src/Mainpage.dox index 7b96e9e556..a02a96f450 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/Mainpage.dox +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/Mainpage.dox @@ -1,7 +1,7 @@ /** @mainpage libKDcraw -libKDcraw is a thread-safe wrapper around libraw. Have a look at KDcrawIface::KDcraw to get started. +libKDcraw is a thread-safe wrapper around libraw. Have a look at KDcrawIface::KDcraw to get started. @see KDcrawIface::KDcraw */ diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawinfocontainer.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawinfocontainer.cpp index b82ac4c653..65655df64f 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawinfocontainer.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawinfocontainer.cpp @@ -1,173 +1,173 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2007-05-02 * @brief RAW file identification information container * * @author Copyright (C) 2007-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ // Local includes #include "dcrawinfocontainer.h" namespace KDcrawIface { DcrawInfoContainer::DcrawInfoContainer() { sensitivity = -1.0; exposureTime = -1.0; aperture = -1.0; focalLength = -1.0; pixelAspectRatio = 1.0; // Default value. This can be unavailable (depending of camera model). rawColors = -1; rawImages = -1; hasIccProfile = false; isDecodable = false; daylightMult[0] = 0.0; daylightMult[1] = 0.0; daylightMult[2] = 0.0; cameraMult[0] = 0.0; cameraMult[1] = 0.0; cameraMult[2] = 0.0; cameraMult[3] = 0.0; blackPoint = 0; for (int ch=0; ch<4; ch++) { blackPointCh[ch] = 0; } whitePoint = 0; topMargin = 0; leftMargin = 0; orientation = ORIENTATION_NONE; for (int x=0 ; x<3 ; x++) { for (int y=0 ; y<4 ; y++) { cameraColorMatrix1[x][y] = 0.0; cameraColorMatrix2[x][y] = 0.0; cameraXYZMatrix[y][x] = 0.0; // NOTE: see B.K.O # 253911 : [y][x] not [x][y] } } } DcrawInfoContainer::~DcrawInfoContainer() { } bool DcrawInfoContainer::isEmpty() { if (make.isEmpty() && model.isEmpty() && filterPattern.isEmpty() && colorKeys.isEmpty() && DNGVersion.isEmpty() && exposureTime == -1.0 && aperture == -1.0 && focalLength == -1.0 && pixelAspectRatio == 1.0 && sensitivity == -1.0 && rawColors == -1 && rawImages == -1 && blackPoint == 0 && blackPointCh[0] == 0 && blackPointCh[1] == 0 && blackPointCh[2] == 0 && blackPointCh[3] == 0 && whitePoint == 0 && topMargin == 0 && leftMargin == 0 && !dateTime.isValid() && !imageSize.isValid() && !fullSize.isValid() && !outputSize.isValid() && !thumbSize.isValid() && cameraColorMatrix1[0][0] == 0.0 && cameraColorMatrix1[0][1] == 0.0 && cameraColorMatrix1[0][2] == 0.0 && cameraColorMatrix1[0][3] == 0.0 && cameraColorMatrix1[1][0] == 0.0 && cameraColorMatrix1[1][1] == 0.0 && cameraColorMatrix1[1][2] == 0.0 && cameraColorMatrix1[1][3] == 0.0 && cameraColorMatrix1[2][0] == 0.0 && cameraColorMatrix1[2][1] == 0.0 && cameraColorMatrix1[2][2] == 0.0 && cameraColorMatrix1[2][3] == 0.0 && cameraColorMatrix2[0][0] == 0.0 && cameraColorMatrix2[0][1] == 0.0 && cameraColorMatrix2[0][2] == 0.0 && cameraColorMatrix2[0][3] == 0.0 && cameraColorMatrix2[1][0] == 0.0 && cameraColorMatrix2[1][1] == 0.0 && cameraColorMatrix2[1][2] == 0.0 && cameraColorMatrix2[1][3] == 0.0 && cameraColorMatrix2[2][0] == 0.0 && cameraColorMatrix2[2][1] == 0.0 && cameraColorMatrix2[2][2] == 0.0 && cameraColorMatrix2[2][3] == 0.0 && cameraXYZMatrix[0][0] == 0.0 && cameraXYZMatrix[0][1] == 0.0 && cameraXYZMatrix[0][2] == 0.0 && cameraXYZMatrix[1][0] == 0.0 && cameraXYZMatrix[1][1] == 0.0 && cameraXYZMatrix[1][2] == 0.0 && cameraXYZMatrix[2][0] == 0.0 && cameraXYZMatrix[2][1] == 0.0 && cameraXYZMatrix[2][2] == 0.0 && cameraXYZMatrix[3][0] == 0.0 && cameraXYZMatrix[3][1] == 0.0 && cameraXYZMatrix[3][2] == 0.0 && orientation == ORIENTATION_NONE ) { return true; } else { return false; } } QDebug operator<<(QDebug dbg, const DcrawInfoContainer& c) { dbg.nospace() << "DcrawInfoContainer::sensitivity: " << c.sensitivity << ", "; dbg.nospace() << "DcrawInfoContainer::exposureTime: " << c.exposureTime << ", "; dbg.nospace() << "DcrawInfoContainer::aperture: " << c.aperture << ", "; dbg.nospace() << "DcrawInfoContainer::focalLength: " << c.focalLength << ", "; dbg.nospace() << "DcrawInfoContainer::pixelAspectRatio: " << c.pixelAspectRatio << ", "; dbg.nospace() << "DcrawInfoContainer::rawColors: " << c.rawColors << ", "; dbg.nospace() << "DcrawInfoContainer::rawImages: " << c.rawImages << ", "; dbg.nospace() << "DcrawInfoContainer::hasIccProfile: " << c.hasIccProfile << ", "; dbg.nospace() << "DcrawInfoContainer::isDecodable: " << c.isDecodable << ", "; dbg.nospace() << "DcrawInfoContainer::daylightMult: " << c.daylightMult << ", "; dbg.nospace() << "DcrawInfoContainer::cameraMult: " << c.cameraMult << ", "; dbg.nospace() << "DcrawInfoContainer::blackPoint: " << c.blackPoint << ", "; dbg.nospace() << "DcrawInfoContainer::whitePoint: " << c.whitePoint << ", "; dbg.nospace() << "DcrawInfoContainer::topMargin: " << c.topMargin << ", "; dbg.nospace() << "DcrawInfoContainer::leftMargin: " << c.leftMargin << ", "; dbg.nospace() << "DcrawInfoContainer::orientation: " << c.orientation; return dbg.space(); } } // namespace KDcrawIface diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawinfocontainer.h b/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawinfocontainer.h index bf0773a78b..739b9ef4c5 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawinfocontainer.h +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawinfocontainer.h @@ -1,158 +1,158 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2007-05-02 * @brief RAW file identification information container * * @author Copyright (C) 2007-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DCRAW_INFO_CONTAINER_H #define DCRAW_INFO_CONTAINER_H // Qt includes #include #include #include #include // Local includes namespace KDcrawIface { class DcrawInfoContainer { public: /** The RAW image orientation values */ enum ImageOrientation { ORIENTATION_NONE = 0, ORIENTATION_180 = 3, ORIENTATION_Mirror90CCW = 4, ORIENTATION_90CCW = 5, ORIENTATION_90CW = 6 }; public: /** Standard constructor */ DcrawInfoContainer(); /** Standard destructor */ virtual ~DcrawInfoContainer(); /** Return 'true' if container is empty, else 'false' */ bool isEmpty(); public: /** True if RAW file include an ICC color profile. */ bool hasIccProfile; /** True is RAW file is decodable by dcraw. */ bool isDecodable; /** The number of RAW colors. */ int rawColors; /** The number of RAW images. */ int rawImages; /** Black level from Raw histogram. */ unsigned int blackPoint; /** Channel black levels from Raw histogram. */ unsigned int blackPointCh[4]; /** White level from Raw histogram. */ unsigned int whitePoint; /** Top margin of raw image. */ unsigned int topMargin; /** Left margin of raw image. */ unsigned int leftMargin; /** The raw image orientation */ ImageOrientation orientation; /** The sensitivity in ISO used by camera to take the picture. */ float sensitivity; /** ==> 1/exposureTime = exposure time in seconds. */ float exposureTime; /** ==> Aperture value in APEX. */ float aperture; /** ==> Focal Length value in mm. */ float focalLength; /** The pixel Aspect Ratio if != 1.0. NOTE: if == 1.0, dcraw do not show this value. */ float pixelAspectRatio; /** White color balance settings. */ double daylightMult[3]; /** Camera multipliers used for White Balance adjustments */ double cameraMult[4]; /** Camera Color Matrix */ float cameraColorMatrix1[3][4]; float cameraColorMatrix2[3][4]; float cameraXYZMatrix[4][3]; /** The used Color Keys */ QString colorKeys; /** The camera maker. */ QString make; /** The camera model. */ QString model; /** The artist name who have picture owner. */ QString owner; /** The demosaising filter pattern. */ QString filterPattern; /** The DNG version. NOTE: it is only shown with DNG RAW files. */ QString DNGVersion; /** Date & time when the picture has been taken. */ QDateTime dateTime; /** The image dimensions in pixels. */ QSize imageSize; /** The thumb dimensions in pixels. */ QSize thumbSize; /** The full RAW image dimensions in pixels. */ QSize fullSize; /** The output dimensions in pixels. */ QSize outputSize; }; //! qDebug() stream operator. Writes container @a c to the debug output in a nicely formatted way. QDebug operator<<(QDebug dbg, const DcrawInfoContainer& c); } // namespace KDcrawIface #endif /* DCRAW_INFO_CONTAINER_H */ diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.cpp index a7d64ce10b..7b242d2b10 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.cpp @@ -1,1324 +1,1324 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2006-09-13 * @brief LibRaw settings widgets * * @author Copyright (C) 2006-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2006-2011 by Marcel Wiesweg * marcel dot wiesweg at gmx dot de * @author Copyright (C) 2007-2008 by Guillaume Castagnino * casta at xwing dot info * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "dcrawsettingswidget.h" // C++ includes #include // Qt includes #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "kdcraw.h" #include "rnuminput.h" #include "rcombobox.h" #include "rexpanderbox.h" #include "libkdcraw_debug.h" #include namespace KDcrawIface { class Q_DECL_HIDDEN DcrawSettingsWidget::Private { public: Private() { autoBrightnessBox = 0; sixteenBitsImage = 0; fourColorCheckBox = 0; brightnessLabel = 0; brightnessSpinBox = 0; blackPointCheckBox = 0; blackPointSpinBox = 0; whitePointCheckBox = 0; whitePointSpinBox = 0; whiteBalanceComboBox = 0; whiteBalanceLabel = 0; customWhiteBalanceSpinBox = 0; customWhiteBalanceLabel = 0; customWhiteBalanceGreenSpinBox = 0; customWhiteBalanceGreenLabel = 0; unclipColorLabel = 0; dontStretchPixelsCheckBox = 0; RAWQualityComboBox = 0; RAWQualityLabel = 0; noiseReductionComboBox = 0; NRSpinBox1 = 0; NRSpinBox2 = 0; NRLabel1 = 0; NRLabel2 = 0; enableCACorrectionBox = 0; autoCACorrectionBox = 0; caRedMultSpinBox = 0; caBlueMultSpinBox = 0; caRedMultLabel = 0; caBlueMultLabel = 0; unclipColorComboBox = 0; reconstructLabel = 0; reconstructSpinBox = 0; outputColorSpaceLabel = 0; outputColorSpaceComboBox = 0; demosaicingSettings = 0; whiteBalanceSettings = 0; correctionsSettings = 0; colormanSettings = 0; medianFilterPassesSpinBox = 0; medianFilterPassesLabel = 0; inIccUrlEdit = 0; outIccUrlEdit = 0; inputColorSpaceLabel = 0; inputColorSpaceComboBox = 0; fixColorsHighlightsBox = 0; refineInterpolationBox = 0; noiseReductionLabel = 0; expoCorrectionBox = 0; expoCorrectionShiftSpinBox = 0; expoCorrectionHighlightSpinBox = 0; expoCorrectionShiftLabel = 0; expoCorrectionHighlightLabel = 0; } /** Convert Exposure correction shift E.V value from GUI to Linear value needs by libraw decoder. */ double shiftExpoFromEvToLinear(double ev) const { // From GUI : -2.0EV => 0.25 // +3.0EV => 8.00 return (1.55*ev + 3.35); } /** Convert Exposure correction shift Linear value from liraw decoder to E.V value needs by GUI. */ double shiftExpoFromLinearToEv(double lin) const { // From GUI : 0.25 => -2.0EV // 8.00 => +3.0EV return ((lin-3.35) / 1.55); } public: QWidget* demosaicingSettings; QWidget* whiteBalanceSettings; QWidget* correctionsSettings; QWidget* colormanSettings; QLabel* whiteBalanceLabel; QLabel* customWhiteBalanceLabel; QLabel* customWhiteBalanceGreenLabel; QLabel* brightnessLabel; QLabel* RAWQualityLabel; QLabel* NRLabel1; QLabel* NRLabel2; QLabel* caRedMultLabel; QLabel* caBlueMultLabel; QLabel* unclipColorLabel; QLabel* reconstructLabel; QLabel* inputColorSpaceLabel; QLabel* outputColorSpaceLabel; QLabel* medianFilterPassesLabel; QLabel* noiseReductionLabel; QLabel* expoCorrectionShiftLabel; QLabel* expoCorrectionHighlightLabel; QCheckBox* blackPointCheckBox; QCheckBox* whitePointCheckBox; QCheckBox* sixteenBitsImage; QCheckBox* autoBrightnessBox; QCheckBox* fourColorCheckBox; QCheckBox* dontStretchPixelsCheckBox; QCheckBox* enableCACorrectionBox; QCheckBox* autoCACorrectionBox; QCheckBox* fixColorsHighlightsBox; QCheckBox* refineInterpolationBox; QCheckBox* expoCorrectionBox; RFileSelector* inIccUrlEdit; RFileSelector* outIccUrlEdit; RComboBox* noiseReductionComboBox; RComboBox* whiteBalanceComboBox; RComboBox* RAWQualityComboBox; RComboBox* unclipColorComboBox; RComboBox* inputColorSpaceComboBox; RComboBox* outputColorSpaceComboBox; RIntNumInput* customWhiteBalanceSpinBox; RIntNumInput* reconstructSpinBox; RIntNumInput* blackPointSpinBox; RIntNumInput* whitePointSpinBox; RIntNumInput* NRSpinBox1; RIntNumInput* NRSpinBox2; RIntNumInput* medianFilterPassesSpinBox; RDoubleNumInput* customWhiteBalanceGreenSpinBox; RDoubleNumInput* caRedMultSpinBox; RDoubleNumInput* caBlueMultSpinBox; RDoubleNumInput* brightnessSpinBox; RDoubleNumInput* expoCorrectionShiftSpinBox; RDoubleNumInput* expoCorrectionHighlightSpinBox; }; DcrawSettingsWidget::DcrawSettingsWidget(QWidget* const parent, int advSettings) : RExpanderBox(parent), d(new Private) { setup(advSettings); } void DcrawSettingsWidget::setup(int advSettings) { setObjectName( QLatin1String("DCRawSettings Expander" )); // --------------------------------------------------------------- // DEMOSAICING Settings panel d->demosaicingSettings = new QWidget(this); QGridLayout* const demosaicingLayout = new QGridLayout(d->demosaicingSettings); int line = 0; d->sixteenBitsImage = new QCheckBox(i18nc("@option:check", "16 bits color depth"), d->demosaicingSettings); d->sixteenBitsImage->setToolTip(i18nc("@info:whatsthis", "

    If enabled, all RAW files will " "be decoded in 16-bit color depth using a linear gamma curve. To " "prevent dark picture rendering in the editor, it is recommended to " "use Color Management in this mode.

    " "

    If disabled, all RAW files will be decoded in 8-bit color " "depth with a BT.709 gamma curve and a 99th-percentile white point. " "This mode is faster than 16-bit decoding.

    ")); demosaicingLayout->addWidget(d->sixteenBitsImage, 0, 0, 1, 2); if (advSettings & SIXTEENBITS) { d->sixteenBitsImage->show(); line = 1; } else { d->sixteenBitsImage->hide(); } d->fourColorCheckBox = new QCheckBox(i18nc("@option:check", "Interpolate RGB as four colors"), d->demosaicingSettings); d->fourColorCheckBox->setToolTip(i18nc("@info:whatsthis", "Interpolate RGB as four " "colors" "

    The default is to assume that all green pixels are the same. " "If even-row green pixels are more sensitive to ultraviolet light " "than odd-row this difference causes a mesh pattern in the output; " "using this option solves this problem with minimal loss of detail.

    " "

    To resume, this option blurs the image a little, but it " "eliminates false 2x2 mesh patterns with VNG quality method or " "mazes with AHD quality method.

    ")); demosaicingLayout->addWidget(d->fourColorCheckBox, line, 0, 1, line == 0 ? 2 : 3); line++; QLabel* const dcrawVersion = new QLabel(d->demosaicingSettings); dcrawVersion->setAlignment(Qt::AlignRight); dcrawVersion->setToolTip(i18nc("@info:tooltip", "Visit LibRaw project website")); dcrawVersion->setOpenExternalLinks(true); dcrawVersion->setTextFormat(Qt::RichText); dcrawVersion->setTextInteractionFlags(Qt::LinksAccessibleByMouse); dcrawVersion->setText(QString::fromLatin1("%2") - .arg(QLatin1String("http://www.libraw.org")) + .arg(QLatin1String("https://www.libraw.org")) .arg(QString::fromLatin1("libraw %1").arg(KDcraw::librawVersion()))); demosaicingLayout->addWidget(dcrawVersion, 0, 2, 1, 1); d->dontStretchPixelsCheckBox = new QCheckBox(i18nc("@option:check", "Do not stretch or rotate pixels"), d->demosaicingSettings); d->dontStretchPixelsCheckBox->setToolTip(i18nc("@info:whatsthis", "Do not stretch or rotate pixels" "

    For Fuji Super CCD cameras, show the image tilted 45 degrees. " "For cameras with non-square pixels, do not stretch the image to " "its correct aspect ratio. In any case, this option guarantees that " "each output pixel corresponds to one RAW pixel.

    ")); demosaicingLayout->addWidget(d->dontStretchPixelsCheckBox, line, 0, 1, 3); line++; d->RAWQualityLabel = new QLabel(i18nc("@label:listbox", "Quality:"), d->demosaicingSettings); d->RAWQualityComboBox = new RComboBox(d->demosaicingSettings); // Original dcraw demosaicing methods d->RAWQualityComboBox->insertItem(RawDecodingSettings::BILINEAR, i18nc("@item:inlistbox Quality", "Bilinear")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::VNG, i18nc("@item:inlistbox Quality", "VNG")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::PPG, i18nc("@item:inlistbox Quality", "PPG")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::AHD, i18nc("@item:inlistbox Quality", "AHD")); // Extended demosaicing method from GPL2 pack d->RAWQualityComboBox->insertItem(RawDecodingSettings::DCB, i18nc("@item:inlistbox Quality", "DCB")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::PL_AHD, i18nc("@item:inlistbox Quality", "AHD v2")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::AFD, i18nc("@item:inlistbox Quality", "AFD")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::VCD, i18nc("@item:inlistbox Quality", "VCD")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::VCD_AHD, i18nc("@item:inlistbox Quality", "VCD & AHD")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::LMMSE, i18nc("@item:inlistbox Quality", "LMMSE")); // Extended demosaicing method from GPL3 pack d->RAWQualityComboBox->insertItem(RawDecodingSettings::AMAZE, i18nc("@item:inlistbox Quality", "AMaZE")); // If Libraw do not support GPL2 pack, disable entries relevant. if (!KDcraw::librawUseGPL2DemosaicPack()) { for (int i=RawDecodingSettings::DCB ; i <=RawDecodingSettings::LMMSE ; ++i) d->RAWQualityComboBox->combo()->setItemData(i, false, Qt::UserRole-1); } // If Libraw do not support GPL3 pack, disable entries relevant. if (!KDcraw::librawUseGPL3DemosaicPack()) { d->RAWQualityComboBox->combo()->setItemData(RawDecodingSettings::AMAZE, false, Qt::UserRole-1); } d->RAWQualityComboBox->setDefaultIndex(RawDecodingSettings::BILINEAR); d->RAWQualityComboBox->setCurrentIndex(RawDecodingSettings::BILINEAR); d->RAWQualityComboBox->setToolTip(i18nc("@info:whatsthis", "Quality (interpolation)" "

    Select here the demosaicing method to use when decoding RAW " "images. A demosaicing algorithm is a digital image process used to " "interpolate a complete image from the partial raw data received " "from the color-filtered image sensor, internal to many digital " "cameras, in form of a matrix of colored pixels. Also known as CFA " "interpolation or color reconstruction, another common spelling is " "demosaicing. The following methods are available for demosaicing " "RAW images:

    " // Original dcraw demosaicing methods "

    • Bilinear: use " "high-speed but low-quality bilinear interpolation (default - for " "slow computers). In this method, the red value of a non-red pixel " "is computed as the average of the adjacent red pixels, and similarly " "for blue and green.
    • " "
    • VNG: use Variable Number " "of Gradients interpolation. This method computes gradients near " "the pixel of interest and uses the lower gradients (representing " "smoother and more similar parts of the image) to make an estimate.
    • " "
    • PPG: use Patterned-Pixel-" "Grouping interpolation. Pixel Grouping uses assumptions about " "natural scenery in making estimates. It has fewer color artifacts " "on natural images than the Variable Number of Gradients method.
    • " "
    • AHD: use Adaptive " "Homogeneity-Directed interpolation. This method selects the " "direction of interpolation so as to maximize a homogeneity metric, " "thus typically minimizing color artifacts.
    • " // Extended demosaicing method "
    • DCB: DCB interpolation from " "linuxphoto.org project.
    • " "
    • AHD v2: modified AHD " "interpolation using Variance of Color Differences method.
    • " "
    • AFD: Adaptive Filtered " "Demosaicing interpolation through 5 pass median filter from PerfectRaw " "project.
    • " "
    • VCD: Variance of Color " "Differences interpolation.
    • " "
    • VCD & AHD: Mixed demosaicing " "between VCD and AHD.
    • " "
    • LMMSE: color demosaicing via " "directional linear minimum mean-square error estimation interpolation " "from PerfectRaw.
    • " "
    • AMaZE: Aliasing Minimization " "interpolation and Zipper Elimination to apply color aberration removal " "from RawTherapee project.

    " "

    Note: some methods can be unavailable if RAW decoder have been built " "without extension packs.

    ")); demosaicingLayout->addWidget(d->RAWQualityLabel, line, 0, 1, 1); demosaicingLayout->addWidget(d->RAWQualityComboBox, line, 1, 1, 2); line++; d->medianFilterPassesSpinBox = new RIntNumInput(d->demosaicingSettings); d->medianFilterPassesSpinBox->setRange(0, 10, 1); d->medianFilterPassesSpinBox->setDefaultValue(0); d->medianFilterPassesLabel = new QLabel(i18nc("@label:slider", "Pass:"), d->whiteBalanceSettings); d->medianFilterPassesSpinBox->setToolTip( i18nc("@info:whatsthis", "Pass" "

    Set here the passes used by the median filter applied after " "interpolation to Red-Green and Blue-Green channels.

    " "

    This setting is only available for specific Quality options: " "Bilinear, " "VNG, PPG, " "AHD, " "DCB, and VCD & AHD.

    ")); demosaicingLayout->addWidget(d->medianFilterPassesLabel, line, 0, 1, 1); demosaicingLayout->addWidget(d->medianFilterPassesSpinBox, line, 1, 1, 2); line++; d->refineInterpolationBox = new QCheckBox(i18nc("@option:check", "Refine interpolation"), d->demosaicingSettings); d->refineInterpolationBox->setToolTip(i18nc("@info:whatsthis", "Refine interpolation" "

    This setting is available only for few Quality options:

    " "

    • DCB: turn on " "the enhance interpolated colors filter.
    • " "
    • VCD & AHD: turn on the " "enhanced effective color interpolation (EECI) refine to improve " "sharpness.

    ")); demosaicingLayout->addWidget(d->refineInterpolationBox, line, 0, 1, 2); // If Libraw do not support GPL2 pack, disable options relevant. if (!KDcraw::librawUseGPL2DemosaicPack()) { d->medianFilterPassesLabel->setEnabled(false); d->medianFilterPassesSpinBox->setEnabled(false); d->refineInterpolationBox->setEnabled(false); } addItem(d->demosaicingSettings, KisIconUtils::loadIcon("kdcraw").pixmap(16, 16), i18nc("@label", "Demosaicing"), QString("demosaicing"), true); // --------------------------------------------------------------- // WHITE BALANCE Settings Panel d->whiteBalanceSettings = new QWidget(this); QGridLayout* const whiteBalanceLayout = new QGridLayout(d->whiteBalanceSettings); d->whiteBalanceLabel = new QLabel(i18nc("@label:listbox", "Method:"), d->whiteBalanceSettings); d->whiteBalanceComboBox = new RComboBox(d->whiteBalanceSettings); d->whiteBalanceComboBox->insertItem(RawDecodingSettings::NONE, i18nc("@item:inlistbox", "Default D65")); d->whiteBalanceComboBox->insertItem(RawDecodingSettings::CAMERA, i18nc("@item:inlistbox", "Camera")); d->whiteBalanceComboBox->insertItem(RawDecodingSettings::AUTO, i18nc("@item:inlistbox set while balance automatically", "Automatic")); d->whiteBalanceComboBox->insertItem(RawDecodingSettings::CUSTOM, i18nc("@item:inlistbox set white balance manually", "Manual")); d->whiteBalanceComboBox->setDefaultIndex(RawDecodingSettings::CAMERA); d->whiteBalanceComboBox->setToolTip(i18nc("@info:whatsthis", "White Balance" "

    Configure the raw white balance:

    " "

    • Default D65: " "Use a standard daylight D65 white balance.
    • " "
    • Camera: Use the white " "balance specified by the camera. If not available, reverts to " "default neutral white balance.
    • " "
    • Automatic: Calculates an " "automatic white balance averaging the entire image.
    • " "
    • Manual: Set a custom " "temperature and green level values.

    ")); d->customWhiteBalanceSpinBox = new RIntNumInput(d->whiteBalanceSettings); d->customWhiteBalanceSpinBox->setRange(2000, 12000, 10); d->customWhiteBalanceSpinBox->setDefaultValue(6500); d->customWhiteBalanceLabel = new QLabel(i18nc("@label:slider", "T(K):"), d->whiteBalanceSettings); d->customWhiteBalanceSpinBox->setToolTip( i18nc("@info:whatsthis", "Temperature" "

    Set here the color temperature in Kelvin.

    ")); d->customWhiteBalanceGreenSpinBox = new RDoubleNumInput(d->whiteBalanceSettings); d->customWhiteBalanceGreenSpinBox->setDecimals(2); d->customWhiteBalanceGreenSpinBox->setRange(0.2, 2.5, 0.01); d->customWhiteBalanceGreenSpinBox->setDefaultValue(1.0); d->customWhiteBalanceGreenLabel = new QLabel(i18nc("@label:slider Green component", "Green:"), d->whiteBalanceSettings); d->customWhiteBalanceGreenSpinBox->setToolTip(i18nc("@info:whatsthis", "

    Set here the " "green component to set magenta color cast removal level.

    ")); d->unclipColorLabel = new QLabel(i18nc("@label:listbox", "Highlights:"), d->whiteBalanceSettings); d->unclipColorComboBox = new RComboBox(d->whiteBalanceSettings); d->unclipColorComboBox->insertItem(0, i18nc("@item:inlistbox", "Solid white")); d->unclipColorComboBox->insertItem(1, i18nc("@item:inlistbox", "Unclip")); d->unclipColorComboBox->insertItem(2, i18nc("@item:inlistbox", "Blend")); d->unclipColorComboBox->insertItem(3, i18nc("@item:inlistbox", "Rebuild")); d->unclipColorComboBox->setDefaultIndex(0); d->unclipColorComboBox->setToolTip(i18nc("@info:whatsthis", "Highlights" "

    Select here the highlight clipping method:

    " "

    • Solid white: " "clip all highlights to solid white
    • " "
    • Unclip: leave highlights " "unclipped in various shades of pink
    • " "
    • Blend:Blend clipped and " "unclipped values together for a gradual fade to white
    • " "
    • Rebuild: reconstruct " "highlights using a level value

    ")); d->reconstructLabel = new QLabel(i18nc("@label:slider Highlight reconstruct level", "Level:"), d->whiteBalanceSettings); d->reconstructSpinBox = new RIntNumInput(d->whiteBalanceSettings); d->reconstructSpinBox->setRange(0, 6, 1); d->reconstructSpinBox->setDefaultValue(0); d->reconstructSpinBox->setToolTip(i18nc("@info:whatsthis", "Level" "

    Specify the reconstruct highlight level. Low values favor " "whites and high values favor colors.

    ")); d->expoCorrectionBox = new QCheckBox(i18nc("@option:check", "Exposure Correction (E.V)"), d->whiteBalanceSettings); d->expoCorrectionBox->setToolTip(i18nc("@info:whatsthis", "

    Turn on the exposure " "correction before interpolation.

    ")); d->expoCorrectionShiftLabel = new QLabel(i18nc("@label:slider", "Linear Shift:"), d->whiteBalanceSettings); d->expoCorrectionShiftSpinBox = new RDoubleNumInput(d->whiteBalanceSettings); d->expoCorrectionShiftSpinBox->setDecimals(2); d->expoCorrectionShiftSpinBox->setRange(-2.0, 3.0, 0.01); d->expoCorrectionShiftSpinBox->setDefaultValue(0.0); d->expoCorrectionShiftSpinBox->setToolTip(i18nc("@info:whatsthis", "Shift" "

    Linear Shift of exposure correction before interpolation in E.V

    ")); d->expoCorrectionHighlightLabel = new QLabel(i18nc("@label:slider", "Highlight:"), d->whiteBalanceSettings); d->expoCorrectionHighlightSpinBox = new RDoubleNumInput(d->whiteBalanceSettings); d->expoCorrectionHighlightSpinBox->setDecimals(2); d->expoCorrectionHighlightSpinBox->setRange(0.0, 1.0, 0.01); d->expoCorrectionHighlightSpinBox->setDefaultValue(0.0); d->expoCorrectionHighlightSpinBox->setToolTip(i18nc("@info:whatsthis", "Highlight" "

    Amount of highlight preservation for exposure correction " "before interpolation in E.V. Only take effect if Shift Correction is > 1.0 E.V

    ")); d->fixColorsHighlightsBox = new QCheckBox(i18nc("@option:check", "Correct false colors in highlights"), d->whiteBalanceSettings); d->fixColorsHighlightsBox->setToolTip(i18nc("@info:whatsthis", "

    If enabled, images with " "overblown channels are processed much more accurately, without " "'pink clouds' (and blue highlights under tungsten lamps).

    ")); d->autoBrightnessBox = new QCheckBox(i18nc("@option:check", "Auto Brightness"), d->whiteBalanceSettings); d->autoBrightnessBox->setToolTip(i18nc("@info:whatsthis", "

    If disable, use a fixed white level " "and ignore the image histogram to adjust brightness.

    ")); d->brightnessLabel = new QLabel(i18nc("@label:slider", "Brightness:"), d->whiteBalanceSettings); d->brightnessSpinBox = new RDoubleNumInput(d->whiteBalanceSettings); d->brightnessSpinBox->setDecimals(2); d->brightnessSpinBox->setRange(0.0, 10.0, 0.01); d->brightnessSpinBox->setDefaultValue(1.0); d->brightnessSpinBox->setToolTip(i18nc("@info:whatsthis", "Brightness" "

    Specify the brightness level of output image. The default " "value is 1.0 (works in 8-bit mode only).

    ")); if (! (advSettings & POSTPROCESSING)) { d->brightnessLabel->hide(); d->brightnessSpinBox->hide(); } d->blackPointCheckBox = new QCheckBox(i18nc("@option:check Black point", "Black:"), d->whiteBalanceSettings); d->blackPointCheckBox->setToolTip(i18nc("@info:whatsthis", "Black point" "

    Use a specific black point value to decode RAW pictures. If " "you set this option to off, the Black Point value will be " "automatically computed.

    ")); d->blackPointSpinBox = new RIntNumInput(d->whiteBalanceSettings); d->blackPointSpinBox->setRange(0, 1000, 1); d->blackPointSpinBox->setDefaultValue(0); d->blackPointSpinBox->setToolTip(i18nc("@info:whatsthis", "Black point value" "

    Specify specific black point value of the output image.

    ")); d->whitePointCheckBox = new QCheckBox(i18nc("@option:check White point", "White:"), d->whiteBalanceSettings); d->whitePointCheckBox->setToolTip(i18nc("@info:whatsthis", "White point" "

    Use a specific white point value to decode RAW pictures. If " "you set this option to off, the White Point value will be " "automatically computed.

    ")); d->whitePointSpinBox = new RIntNumInput(d->whiteBalanceSettings); d->whitePointSpinBox->setRange(0, 20000, 1); d->whitePointSpinBox->setDefaultValue(0); d->whitePointSpinBox->setToolTip(i18nc("@info:whatsthis", "White point value" "

    Specify specific white point value of the output image.

    ")); if (! (advSettings & BLACKWHITEPOINTS)) { d->blackPointCheckBox->hide(); d->blackPointSpinBox->hide(); d->whitePointCheckBox->hide(); d->whitePointSpinBox->hide(); } whiteBalanceLayout->addWidget(d->whiteBalanceLabel, 0, 0, 1, 1); whiteBalanceLayout->addWidget(d->whiteBalanceComboBox, 0, 1, 1, 2); whiteBalanceLayout->addWidget(d->customWhiteBalanceLabel, 1, 0, 1, 1); whiteBalanceLayout->addWidget(d->customWhiteBalanceSpinBox, 1, 1, 1, 2); whiteBalanceLayout->addWidget(d->customWhiteBalanceGreenLabel, 2, 0, 1, 1); whiteBalanceLayout->addWidget(d->customWhiteBalanceGreenSpinBox, 2, 1, 1, 2); whiteBalanceLayout->addWidget(d->unclipColorLabel, 3, 0, 1, 1); whiteBalanceLayout->addWidget(d->unclipColorComboBox, 3, 1, 1, 2); whiteBalanceLayout->addWidget(d->reconstructLabel, 4, 0, 1, 1); whiteBalanceLayout->addWidget(d->reconstructSpinBox, 4, 1, 1, 2); whiteBalanceLayout->addWidget(d->expoCorrectionBox, 5, 0, 1, 2); whiteBalanceLayout->addWidget(d->expoCorrectionShiftLabel, 6, 0, 1, 1); whiteBalanceLayout->addWidget(d->expoCorrectionShiftSpinBox, 6, 1, 1, 2); whiteBalanceLayout->addWidget(d->expoCorrectionHighlightLabel, 7, 0, 1, 1); whiteBalanceLayout->addWidget(d->expoCorrectionHighlightSpinBox, 7, 1, 1, 2); whiteBalanceLayout->addWidget(d->fixColorsHighlightsBox, 8, 0, 1, 3); whiteBalanceLayout->addWidget(d->autoBrightnessBox, 9, 0, 1, 3); whiteBalanceLayout->addWidget(d->brightnessLabel, 10, 0, 1, 1); whiteBalanceLayout->addWidget(d->brightnessSpinBox, 10, 1, 1, 2); whiteBalanceLayout->addWidget(d->blackPointCheckBox, 11, 0, 1, 1); whiteBalanceLayout->addWidget(d->blackPointSpinBox, 11, 1, 1, 2); whiteBalanceLayout->addWidget(d->whitePointCheckBox, 12, 0, 1, 1); whiteBalanceLayout->addWidget(d->whitePointSpinBox, 12, 1, 1, 2); whiteBalanceLayout->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); whiteBalanceLayout->setMargin(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); addItem(d->whiteBalanceSettings, KisIconUtils::loadIcon("kdcraw").pixmap(16, 16), i18nc("@label", "White Balance"), QString("whitebalance"), true); // --------------------------------------------------------------- // CORRECTIONS Settings panel d->correctionsSettings = new QWidget(this); QGridLayout* const correctionsLayout = new QGridLayout(d->correctionsSettings); d->noiseReductionLabel = new QLabel(i18nc("@label:listbox", "Noise reduction:"), d->correctionsSettings); d->noiseReductionComboBox = new RComboBox(d->colormanSettings); d->noiseReductionComboBox->insertItem(RawDecodingSettings::NONR, i18nc("@item:inlistbox Noise Reduction", "None")); d->noiseReductionComboBox->insertItem(RawDecodingSettings::WAVELETSNR, i18nc("@item:inlistbox Noise Reduction", "Wavelets")); d->noiseReductionComboBox->insertItem(RawDecodingSettings::FBDDNR, i18nc("@item:inlistbox Noise Reduction", "FBDD")); d->noiseReductionComboBox->insertItem(RawDecodingSettings::LINENR, i18nc("@item:inlistbox Noise Reduction", "CFA Line Denoise")); d->noiseReductionComboBox->insertItem(RawDecodingSettings::IMPULSENR, i18nc("@item:inlistbox Noise Reduction", "Impulse Denoise")); d->noiseReductionComboBox->setDefaultIndex(RawDecodingSettings::NONR); d->noiseReductionComboBox->setToolTip(i18nc("@info:whatsthis", "Noise Reduction" "

    Select here the noise reduction method to apply during RAW " "decoding.

    " "

    • None: no " "noise reduction.
    • " "
    • Wavelets: wavelets " "correction to erase noise while preserving real detail. It's " "applied after interpolation.
    • " "
    • FBDD: Fake Before " "Demosaicing Denoising noise reduction. It's applied before " "interpolation.
    • " "
    • CFA Line Denoise: Banding " "noise suppression. It's applied after interpolation.
    • " "
    • Impulse Denoise: Impulse " "noise suppression. It's applied after interpolation.

    ")); d->NRSpinBox1 = new RIntNumInput(d->correctionsSettings); d->NRSpinBox1->setRange(100, 1000, 1); d->NRSpinBox1->setDefaultValue(100); d->NRLabel1 = new QLabel(d->correctionsSettings); d->NRSpinBox2 = new RIntNumInput(d->correctionsSettings); d->NRSpinBox2->setRange(100, 1000, 1); d->NRSpinBox2->setDefaultValue(100); d->NRLabel2 = new QLabel(d->correctionsSettings); d->enableCACorrectionBox = new QCheckBox(i18nc("@option:check", "Enable Chromatic Aberration correction"), d->correctionsSettings); d->enableCACorrectionBox->setToolTip(i18nc("@info:whatsthis", "Enable Chromatic " "Aberration correction" "

    Enlarge the raw red-green and blue-yellow axis by the given " "factors (automatic by default).

    ")); d->autoCACorrectionBox = new QCheckBox(i18nc("@option:check", "Automatic color axis adjustments"), d->correctionsSettings); d->autoCACorrectionBox->setToolTip(i18nc("@info:whatsthis", "Automatic Chromatic " "Aberration correction" "

    If this option is turned on, it will try to shift image " "channels slightly and evaluate Chromatic Aberration change. Note " "that if you shot blue-red pattern, the method may fail. In this " "case, disable this option and tune manually color factors.

    ")); d->caRedMultLabel = new QLabel(i18nc("@label:slider", "Red-Green:"), d->correctionsSettings); d->caRedMultSpinBox = new RDoubleNumInput(d->correctionsSettings); d->caRedMultSpinBox->setDecimals(1); d->caRedMultSpinBox->setRange(-4.0, 4.0, 0.1); d->caRedMultSpinBox->setDefaultValue(0.0); d->caRedMultSpinBox->setToolTip(i18nc("@info:whatsthis", "Red-Green multiplier" "

    Set here the amount of correction on red-green axis

    ")); d->caBlueMultLabel = new QLabel(i18nc("@label:slider", "Blue-Yellow:"), d->correctionsSettings); d->caBlueMultSpinBox = new RDoubleNumInput(d->correctionsSettings); d->caBlueMultSpinBox->setDecimals(1); d->caBlueMultSpinBox->setRange(-4.0, 4.0, 0.1); d->caBlueMultSpinBox->setDefaultValue(0.0); d->caBlueMultSpinBox->setToolTip(i18nc("@info:whatsthis", "Blue-Yellow multiplier" "

    Set here the amount of correction on blue-yellow axis

    ")); correctionsLayout->addWidget(d->noiseReductionLabel, 0, 0, 1, 1); correctionsLayout->addWidget(d->noiseReductionComboBox, 0, 1, 1, 2); correctionsLayout->addWidget(d->NRLabel1, 1, 0, 1, 1); correctionsLayout->addWidget(d->NRSpinBox1, 1, 1, 1, 2); correctionsLayout->addWidget(d->NRLabel2, 2, 0, 1, 1); correctionsLayout->addWidget(d->NRSpinBox2, 2, 1, 1, 2); correctionsLayout->addWidget(d->enableCACorrectionBox, 3, 0, 1, 3); correctionsLayout->addWidget(d->autoCACorrectionBox, 4, 0, 1, 3); correctionsLayout->addWidget(d->caRedMultLabel, 5, 0, 1, 1); correctionsLayout->addWidget(d->caRedMultSpinBox, 5, 1, 1, 2); correctionsLayout->addWidget(d->caBlueMultLabel, 6, 0, 1, 1); correctionsLayout->addWidget(d->caBlueMultSpinBox, 6, 1, 1, 2); correctionsLayout->setRowStretch(7, 10); correctionsLayout->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); correctionsLayout->setMargin(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); addItem(d->correctionsSettings, KisIconUtils::loadIcon("kdcraw").pixmap(16, 16), i18nc("@label", "Corrections"), QString("corrections"), false); // --------------------------------------------------------------- // COLOR MANAGEMENT Settings panel d->colormanSettings = new QWidget(this); QGridLayout* const colormanLayout = new QGridLayout(d->colormanSettings); d->inputColorSpaceLabel = new QLabel(i18nc("@label:listbox", "Camera Profile:"), d->colormanSettings); d->inputColorSpaceComboBox = new RComboBox(d->colormanSettings); d->inputColorSpaceComboBox->insertItem(RawDecodingSettings::NOINPUTCS, i18nc("@item:inlistbox Camera Profile", "None")); d->inputColorSpaceComboBox->insertItem(RawDecodingSettings::EMBEDDED, i18nc("@item:inlistbox Camera Profile", "Embedded")); d->inputColorSpaceComboBox->insertItem(RawDecodingSettings::CUSTOMINPUTCS, i18nc("@item:inlistbox Camera Profile", "Custom")); d->inputColorSpaceComboBox->setDefaultIndex(RawDecodingSettings::NOINPUTCS); d->inputColorSpaceComboBox->setToolTip(i18nc("@info:whatsthis", "Camera Profile" "

    Select here the input color space used to decode RAW data.

    " "

    • None: no " "input color profile is used during RAW decoding.
    • " "
    • Embedded: use embedded " "color profile from RAW file, if it exists.
    • " "
    • Custom: use a custom " "input color space profile.

    ")); d->inIccUrlEdit = new RFileSelector(d->colormanSettings); d->inIccUrlEdit->setFileDlgMode(QFileDialog::ExistingFile); d->inIccUrlEdit->setFileDlgFilter(i18n("ICC Files (*.icc; *.icm)")); d->outputColorSpaceLabel = new QLabel(i18nc("@label:listbox", "Workspace:"), d->colormanSettings); d->outputColorSpaceComboBox = new RComboBox( d->colormanSettings ); d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::RAWCOLOR, i18nc("@item:inlistbox Workspace", "Raw (no profile)")); d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::SRGB, i18nc("@item:inlistbox Workspace", "sRGB")); d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::ADOBERGB, i18nc("@item:inlistbox Workspace", "Adobe RGB")); d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::WIDEGAMMUT, i18nc("@item:inlistbox Workspace", "Wide Gamut")); d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::PROPHOTO, i18nc("@item:inlistbox Workspace", "Pro-Photo")); d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::CUSTOMOUTPUTCS, i18nc("@item:inlistbox Workspace", "Custom")); d->outputColorSpaceComboBox->setDefaultIndex(RawDecodingSettings::SRGB); d->outputColorSpaceComboBox->setToolTip(i18nc("@info:whatsthis", "Workspace" "

    Select here the output color space used to decode RAW data.

    " "

    • Raw (no profile): " "in this mode, no output color space is used during RAW decoding.
    • " "
    • sRGB: this is an RGB " "color space, created cooperatively by Hewlett-Packard and " "Microsoft. It is the best choice for images destined for the Web " "and portrait photography.
    • " "
    • Adobe RGB: this color " "space is an extended RGB color space, developed by Adobe. It is " "used for photography applications such as advertising and fine " "art.
    • " "
    • Wide Gamut: this color " "space is an expanded version of the Adobe RGB color space.
    • " "
    • Pro-Photo: this color " "space is an RGB color space, developed by Kodak, that offers an " "especially large gamut designed for use with photographic outputs " "in mind.
    • " "
    • Custom: use a custom " "output color space profile.

    ")); d->outIccUrlEdit = new RFileSelector(d->colormanSettings); d->outIccUrlEdit->setFileDlgMode(QFileDialog::ExistingFile); d->outIccUrlEdit->setFileDlgFilter(i18n("ICC Files (*.icc; *.icm)")); colormanLayout->addWidget(d->inputColorSpaceLabel, 0, 0, 1, 1); colormanLayout->addWidget(d->inputColorSpaceComboBox, 0, 1, 1, 2); colormanLayout->addWidget(d->inIccUrlEdit, 1, 0, 1, 3); colormanLayout->addWidget(d->outputColorSpaceLabel, 2, 0, 1, 1); colormanLayout->addWidget(d->outputColorSpaceComboBox, 2, 1, 1, 2); colormanLayout->addWidget(d->outIccUrlEdit, 3, 0, 1, 3); colormanLayout->setRowStretch(4, 10); colormanLayout->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); colormanLayout->setMargin(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); addItem(d->colormanSettings, KisIconUtils::loadIcon("kdcraw").pixmap(16, 16), i18nc("@label", "Color Management"), QString("colormanagement"), false); if (! (advSettings & COLORSPACE)) removeItem(COLORMANAGEMENT); addStretch(); // --------------------------------------------------------------- connect(d->unclipColorComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::slotUnclipColorActivated); connect(d->whiteBalanceComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::slotWhiteBalanceToggled); connect(d->noiseReductionComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::slotNoiseReductionChanged); connect(d->enableCACorrectionBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::slotCACorrectionToggled); connect(d->autoCACorrectionBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::slotAutoCAToggled); connect(d->blackPointCheckBox, SIGNAL(toggled(bool)), d->blackPointSpinBox, SLOT(setEnabled(bool))); connect(d->whitePointCheckBox, SIGNAL(toggled(bool)), d->whitePointSpinBox, SLOT(setEnabled(bool))); connect(d->sixteenBitsImage, &QCheckBox::toggled, this, &DcrawSettingsWidget::slotsixteenBitsImageToggled); connect(d->inputColorSpaceComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::slotInputColorSpaceChanged); connect(d->outputColorSpaceComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::slotOutputColorSpaceChanged); connect(d->expoCorrectionBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::slotExposureCorrectionToggled); connect(d->expoCorrectionShiftSpinBox, &RDoubleNumInput::valueChanged, this, &DcrawSettingsWidget::slotExpoCorrectionShiftChanged); // Wrapper to emit signal when something is changed in settings. connect(d->inIccUrlEdit->lineEdit(), &QLineEdit::textChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->outIccUrlEdit->lineEdit(), &QLineEdit::textChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->whiteBalanceComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->RAWQualityComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::slotRAWQualityChanged); connect(d->unclipColorComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->inputColorSpaceComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->outputColorSpaceComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->blackPointCheckBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->whitePointCheckBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->sixteenBitsImage, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->fixColorsHighlightsBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->autoBrightnessBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->fourColorCheckBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->dontStretchPixelsCheckBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->refineInterpolationBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->customWhiteBalanceSpinBox, &RIntNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->reconstructSpinBox, &RIntNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->blackPointSpinBox, &RIntNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->whitePointSpinBox, &RIntNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->NRSpinBox1, &RIntNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->NRSpinBox2, &RIntNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->medianFilterPassesSpinBox, &RIntNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->customWhiteBalanceGreenSpinBox, &RDoubleNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->caRedMultSpinBox, &RDoubleNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->caBlueMultSpinBox, &RDoubleNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->brightnessSpinBox, &RDoubleNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->expoCorrectionHighlightSpinBox, &RDoubleNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); } DcrawSettingsWidget::~DcrawSettingsWidget() { delete d; } void DcrawSettingsWidget::updateMinimumWidth() { int width = 0; for (int i = 0; i < count(); i++) { if (widget(i)->width() > width) width = widget(i)->width(); } setMinimumWidth(width); } RFileSelector* DcrawSettingsWidget::inputProfileUrlEdit() const { return d->inIccUrlEdit; } RFileSelector* DcrawSettingsWidget::outputProfileUrlEdit() const { return d->outIccUrlEdit; } void DcrawSettingsWidget::resetToDefault() { setSettings(RawDecodingSettings()); } void DcrawSettingsWidget::slotsixteenBitsImageToggled(bool b) { setEnabledBrightnessSettings(!b); emit signalSixteenBitsImageToggled(d->sixteenBitsImage->isChecked()); } void DcrawSettingsWidget::slotWhiteBalanceToggled(int v) { if (v == 3) { d->customWhiteBalanceSpinBox->setEnabled(true); d->customWhiteBalanceGreenSpinBox->setEnabled(true); d->customWhiteBalanceLabel->setEnabled(true); d->customWhiteBalanceGreenLabel->setEnabled(true); } else { d->customWhiteBalanceSpinBox->setEnabled(false); d->customWhiteBalanceGreenSpinBox->setEnabled(false); d->customWhiteBalanceLabel->setEnabled(false); d->customWhiteBalanceGreenLabel->setEnabled(false); } } void DcrawSettingsWidget::slotUnclipColorActivated(int v) { if (v == 3) // Reconstruct Highlight method { d->reconstructLabel->setEnabled(true); d->reconstructSpinBox->setEnabled(true); } else { d->reconstructLabel->setEnabled(false); d->reconstructSpinBox->setEnabled(false); } } void DcrawSettingsWidget::slotNoiseReductionChanged(int item) { d->NRSpinBox1->setEnabled(true); d->NRLabel1->setEnabled(true); d->NRSpinBox2->setEnabled(true); d->NRLabel2->setEnabled(true); d->NRLabel1->setText(i18nc("@label", "Threshold:")); d->NRSpinBox1->setToolTip(i18nc("@info:whatsthis", "Threshold" "

    Set here the noise reduction threshold value to use.

    ")); switch(item) { case RawDecodingSettings::WAVELETSNR: case RawDecodingSettings::FBDDNR: case RawDecodingSettings::LINENR: d->NRSpinBox2->setVisible(false); d->NRLabel2->setVisible(false); break; case RawDecodingSettings::IMPULSENR: d->NRLabel1->setText(i18nc("@label", "Luminance:")); d->NRSpinBox1->setToolTip(i18nc("@info:whatsthis", "Luminance" "

    Amount of Luminance impulse noise reduction.

    ")); d->NRLabel2->setText(i18nc("@label", "Chrominance:")); d->NRSpinBox2->setToolTip(i18nc("@info:whatsthis", "Chrominance" "

    Amount of Chrominance impulse noise reduction.

    ")); d->NRSpinBox2->setVisible(true); d->NRLabel2->setVisible(true); break; default: d->NRSpinBox1->setEnabled(false); d->NRLabel1->setEnabled(false); d->NRSpinBox2->setEnabled(false); d->NRLabel2->setEnabled(false); d->NRSpinBox2->setVisible(false); d->NRLabel2->setVisible(false); break; } emit signalSettingsChanged(); } void DcrawSettingsWidget::slotCACorrectionToggled(bool b) { d->autoCACorrectionBox->setEnabled(b); slotAutoCAToggled(d->autoCACorrectionBox->isChecked()); } void DcrawSettingsWidget::slotAutoCAToggled(bool b) { if (b) { d->caRedMultSpinBox->setValue(0.0); d->caBlueMultSpinBox->setValue(0.0); } bool mult = (!b) && (d->autoCACorrectionBox->isEnabled()); d->caRedMultSpinBox->setEnabled(mult); d->caBlueMultSpinBox->setEnabled(mult); d->caRedMultLabel->setEnabled(mult); d->caBlueMultLabel->setEnabled(mult); emit signalSettingsChanged(); } void DcrawSettingsWidget::slotExposureCorrectionToggled(bool b) { d->expoCorrectionShiftLabel->setEnabled(b); d->expoCorrectionShiftSpinBox->setEnabled(b); d->expoCorrectionHighlightLabel->setEnabled(b); d->expoCorrectionHighlightSpinBox->setEnabled(b); slotExpoCorrectionShiftChanged(d->expoCorrectionShiftSpinBox->value()); } void DcrawSettingsWidget::slotExpoCorrectionShiftChanged(double ev) { // Only enable Highligh exposure correction if Shift correction is >= 1.0, else this settings do not take effect. bool b = (ev >= 1.0); d->expoCorrectionHighlightLabel->setEnabled(b); d->expoCorrectionHighlightSpinBox->setEnabled(b); emit signalSettingsChanged(); } void DcrawSettingsWidget::slotInputColorSpaceChanged(int item) { d->inIccUrlEdit->setEnabled(item == RawDecodingSettings::CUSTOMINPUTCS); } void DcrawSettingsWidget::slotOutputColorSpaceChanged(int item) { d->outIccUrlEdit->setEnabled(item == RawDecodingSettings::CUSTOMOUTPUTCS); } void DcrawSettingsWidget::slotRAWQualityChanged(int quality) { switch(quality) { case RawDecodingSettings::DCB: case RawDecodingSettings::VCD_AHD: // These options can be only available if Libraw use GPL2 pack. d->medianFilterPassesLabel->setEnabled(KDcraw::librawUseGPL2DemosaicPack()); d->medianFilterPassesSpinBox->setEnabled(KDcraw::librawUseGPL2DemosaicPack()); d->refineInterpolationBox->setEnabled(KDcraw::librawUseGPL2DemosaicPack()); break; case RawDecodingSettings::PL_AHD: case RawDecodingSettings::AFD: case RawDecodingSettings::VCD: case RawDecodingSettings::LMMSE: case RawDecodingSettings::AMAZE: d->medianFilterPassesLabel->setEnabled(false); d->medianFilterPassesSpinBox->setEnabled(false); d->refineInterpolationBox->setEnabled(false); break; default: // BILINEAR, VNG, PPG, AHD d->medianFilterPassesLabel->setEnabled(true); d->medianFilterPassesSpinBox->setEnabled(true); d->refineInterpolationBox->setEnabled(false); break; } emit signalSettingsChanged(); } void DcrawSettingsWidget::setEnabledBrightnessSettings(bool b) { d->brightnessLabel->setEnabled(b); d->brightnessSpinBox->setEnabled(b); } bool DcrawSettingsWidget::brightnessSettingsIsEnabled() const { return d->brightnessSpinBox->isEnabled(); } void DcrawSettingsWidget::setSettings(const RawDecodingSettings& settings) { d->sixteenBitsImage->setChecked(settings.sixteenBitsImage); switch(settings.whiteBalance) { case RawDecodingSettings::CAMERA: d->whiteBalanceComboBox->setCurrentIndex(1); break; case RawDecodingSettings::AUTO: d->whiteBalanceComboBox->setCurrentIndex(2); break; case RawDecodingSettings::CUSTOM: d->whiteBalanceComboBox->setCurrentIndex(3); break; default: d->whiteBalanceComboBox->setCurrentIndex(0); break; } slotWhiteBalanceToggled(d->whiteBalanceComboBox->currentIndex()); d->customWhiteBalanceSpinBox->setValue(settings.customWhiteBalance); d->customWhiteBalanceGreenSpinBox->setValue(settings.customWhiteBalanceGreen); d->fourColorCheckBox->setChecked(settings.RGBInterpolate4Colors); d->autoBrightnessBox->setChecked(settings.autoBrightness); d->fixColorsHighlightsBox->setChecked(settings.fixColorsHighlights); switch(settings.unclipColors) { case 0: d->unclipColorComboBox->setCurrentIndex(0); break; case 1: d->unclipColorComboBox->setCurrentIndex(1); break; case 2: d->unclipColorComboBox->setCurrentIndex(2); break; default: // Reconstruct Highlight method d->unclipColorComboBox->setCurrentIndex(3); d->reconstructSpinBox->setValue(settings.unclipColors-3); break; } slotUnclipColorActivated(d->unclipColorComboBox->currentIndex()); d->dontStretchPixelsCheckBox->setChecked(settings.DontStretchPixels); d->brightnessSpinBox->setValue(settings.brightness); d->blackPointCheckBox->setChecked(settings.enableBlackPoint); d->blackPointSpinBox->setEnabled(settings.enableBlackPoint); d->blackPointSpinBox->setValue(settings.blackPoint); d->whitePointCheckBox->setChecked(settings.enableWhitePoint); d->whitePointSpinBox->setEnabled(settings.enableWhitePoint); d->whitePointSpinBox->setValue(settings.whitePoint); int q = settings.RAWQuality; // If Libraw do not support GPL2 pack, reset to BILINEAR. if (!KDcraw::librawUseGPL2DemosaicPack()) { for (int i=RawDecodingSettings::DCB ; i <=RawDecodingSettings::LMMSE ; ++i) { if (q == i) { q = RawDecodingSettings::BILINEAR; qCDebug(LIBKDCRAW_LOG) << "Libraw GPL2 pack not available. Raw quality set to Bilinear"; break; } } } // If Libraw do not support GPL3 pack, reset to BILINEAR. if (!KDcraw::librawUseGPL3DemosaicPack() && (q == RawDecodingSettings::AMAZE)) { q = RawDecodingSettings::BILINEAR; qCDebug(LIBKDCRAW_LOG) << "Libraw GPL3 pack not available. Raw quality set to Bilinear"; } d->RAWQualityComboBox->setCurrentIndex(q); switch(q) { case RawDecodingSettings::DCB: d->medianFilterPassesSpinBox->setValue(settings.dcbIterations); d->refineInterpolationBox->setChecked(settings.dcbEnhanceFl); break; case RawDecodingSettings::VCD_AHD: d->medianFilterPassesSpinBox->setValue(settings.eeciRefine); d->refineInterpolationBox->setChecked(settings.eeciRefine); break; default: d->medianFilterPassesSpinBox->setValue(settings.medianFilterPasses); d->refineInterpolationBox->setChecked(false); // option not used. break; } slotRAWQualityChanged(q); d->inputColorSpaceComboBox->setCurrentIndex((int)settings.inputColorSpace); slotInputColorSpaceChanged((int)settings.inputColorSpace); d->outputColorSpaceComboBox->setCurrentIndex((int)settings.outputColorSpace); slotOutputColorSpaceChanged((int)settings.outputColorSpace); d->noiseReductionComboBox->setCurrentIndex(settings.NRType); slotNoiseReductionChanged(settings.NRType); d->NRSpinBox1->setValue(settings.NRThreshold); d->NRSpinBox2->setValue(settings.NRChroThreshold); d->enableCACorrectionBox->setChecked(settings.enableCACorrection); d->caRedMultSpinBox->setValue(settings.caMultiplier[0]); d->caBlueMultSpinBox->setValue(settings.caMultiplier[1]); d->autoCACorrectionBox->setChecked((settings.caMultiplier[0] == 0.0) && (settings.caMultiplier[1] == 0.0)); slotCACorrectionToggled(settings.enableCACorrection); d->expoCorrectionBox->setChecked(settings.expoCorrection); slotExposureCorrectionToggled(settings.expoCorrection); d->expoCorrectionShiftSpinBox->setValue(d->shiftExpoFromLinearToEv(settings.expoCorrectionShift)); d->expoCorrectionHighlightSpinBox->setValue(settings.expoCorrectionHighlight); d->inIccUrlEdit->lineEdit()->setText(settings.inputProfile); d->outIccUrlEdit->lineEdit()->setText(settings.outputProfile); } RawDecodingSettings DcrawSettingsWidget::settings() const { RawDecodingSettings prm; prm.sixteenBitsImage = d->sixteenBitsImage->isChecked(); switch(d->whiteBalanceComboBox->currentIndex()) { case 1: prm.whiteBalance = RawDecodingSettings::CAMERA; break; case 2: prm.whiteBalance = RawDecodingSettings::AUTO; break; case 3: prm.whiteBalance = RawDecodingSettings::CUSTOM; break; default: prm.whiteBalance = RawDecodingSettings::NONE; break; } prm.customWhiteBalance = d->customWhiteBalanceSpinBox->value(); prm.customWhiteBalanceGreen = d->customWhiteBalanceGreenSpinBox->value(); prm.RGBInterpolate4Colors = d->fourColorCheckBox->isChecked(); prm.autoBrightness = d->autoBrightnessBox->isChecked(); prm.fixColorsHighlights = d->fixColorsHighlightsBox->isChecked(); switch(d->unclipColorComboBox->currentIndex()) { case 0: prm.unclipColors = 0; break; case 1: prm.unclipColors = 1; break; case 2: prm.unclipColors = 2; break; default: // Reconstruct Highlight method prm.unclipColors = d->reconstructSpinBox->value()+3; break; } prm.DontStretchPixels = d->dontStretchPixelsCheckBox->isChecked(); prm.brightness = d->brightnessSpinBox->value(); prm.enableBlackPoint = d->blackPointCheckBox->isChecked(); prm.blackPoint = d->blackPointSpinBox->value(); prm.enableWhitePoint = d->whitePointCheckBox->isChecked(); prm.whitePoint = d->whitePointSpinBox->value(); prm.RAWQuality = (RawDecodingSettings::DecodingQuality)d->RAWQualityComboBox->currentIndex(); switch(prm.RAWQuality) { case RawDecodingSettings::DCB: prm.dcbIterations = d->medianFilterPassesSpinBox->value(); prm.dcbEnhanceFl = d->refineInterpolationBox->isChecked(); break; case RawDecodingSettings::VCD_AHD: prm.esMedPasses = d->medianFilterPassesSpinBox->value(); prm.eeciRefine = d->refineInterpolationBox->isChecked(); break; default: prm.medianFilterPasses = d->medianFilterPassesSpinBox->value(); break; } prm.NRType = (RawDecodingSettings::NoiseReduction)d->noiseReductionComboBox->currentIndex(); switch (prm.NRType) { case RawDecodingSettings::NONR: { prm.NRThreshold = 0; prm.NRChroThreshold = 0; break; } case RawDecodingSettings::WAVELETSNR: case RawDecodingSettings::FBDDNR: case RawDecodingSettings::LINENR: { prm.NRThreshold = d->NRSpinBox1->value(); prm.NRChroThreshold = 0; break; } default: // IMPULSENR { prm.NRThreshold = d->NRSpinBox1->value(); prm.NRChroThreshold = d->NRSpinBox2->value(); break; } } prm.enableCACorrection = d->enableCACorrectionBox->isChecked(); prm.caMultiplier[0] = d->caRedMultSpinBox->value(); prm.caMultiplier[1] = d->caBlueMultSpinBox->value(); prm.expoCorrection = d->expoCorrectionBox->isChecked(); prm.expoCorrectionShift = d->shiftExpoFromEvToLinear(d->expoCorrectionShiftSpinBox->value()); prm.expoCorrectionHighlight = d->expoCorrectionHighlightSpinBox->value(); prm.inputColorSpace = (RawDecodingSettings::InputColorSpace)(d->inputColorSpaceComboBox->currentIndex()); prm.outputColorSpace = (RawDecodingSettings::OutputColorSpace)(d->outputColorSpaceComboBox->currentIndex()); prm.inputProfile = d->inIccUrlEdit->lineEdit()->text(); prm.outputProfile = d->outIccUrlEdit->lineEdit()->text(); return prm; } void DcrawSettingsWidget::writeSettings(KConfigGroup& group) { RawDecodingSettings prm = settings(); prm.writeSettings(group); RExpanderBox::writeSettings(group); } void DcrawSettingsWidget::readSettings(KConfigGroup& group) { RawDecodingSettings prm; prm.readSettings(group); setSettings(prm); RExpanderBox::readSettings(group); } } // NameSpace KDcrawIface diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.h b/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.h index 1370cae405..9bca08791a 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.h +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.h @@ -1,126 +1,126 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2006-09-13 * @brief LibRaw settings widgets * * @author Copyright (C) 2006-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2006-2011 by Marcel Wiesweg * marcel dot wiesweg at gmx dot de * @author Copyright (C) 2007-2008 by Guillaume Castagnino * casta at xwing dot info * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DCRAW_SETTINGS_WIDGET_H #define DCRAW_SETTINGS_WIDGET_H // Qt includes #include // KDE includes #include // Local includes #include "rawdecodingsettings.h" #include "rexpanderbox.h" #include "rwidgetutils.h" namespace KDcrawIface { class DcrawSettingsWidget : public RExpanderBox { Q_OBJECT public: enum AdvancedSettingsOptions { SIXTEENBITS = 0x00000001, COLORSPACE = 0x00000002, POSTPROCESSING = 0x00000004, BLACKWHITEPOINTS = 0x00000008 }; enum SettingsTabs { DEMOSAICING = 0, WHITEBALANCE, CORRECTIONS, COLORMANAGEMENT }; public: /** * @param advSettings the default value is COLORSPACE */ explicit DcrawSettingsWidget(QWidget* const parent, int advSettings = COLORSPACE); ~DcrawSettingsWidget() override; RFileSelector* inputProfileUrlEdit() const; RFileSelector* outputProfileUrlEdit() const; void setup(int advSettings); void setEnabledBrightnessSettings(bool b); bool brightnessSettingsIsEnabled() const; void updateMinimumWidth(); void resetToDefault(); void setSettings(const RawDecodingSettings& settings); RawDecodingSettings settings() const; void readSettings(KConfigGroup& group) override; void writeSettings(KConfigGroup& group) override; Q_SIGNALS: void signalSixteenBitsImageToggled(bool); void signalSettingsChanged(); private Q_SLOTS: void slotWhiteBalanceToggled(int); void slotsixteenBitsImageToggled(bool); void slotUnclipColorActivated(int); void slotNoiseReductionChanged(int); void slotCACorrectionToggled(bool); void slotExposureCorrectionToggled(bool); void slotAutoCAToggled(bool); void slotInputColorSpaceChanged(int); void slotOutputColorSpaceChanged(int); void slotRAWQualityChanged(int); void slotExpoCorrectionShiftChanged(double); private: class Private; Private* const d; }; } // NameSpace KDcrawIface #endif /* DCRAW_SETTINGS_WIDGET_H */ diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.cpp index 3ca5011b3f..dcaecc6fa7 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.cpp @@ -1,583 +1,583 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2006-12-09 * @brief a tread-safe libraw C++ program interface * * @author Copyright (C) 2006-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2006-2013 by Marcel Wiesweg * marcel dot wiesweg at gmx dot de * @author Copyright (C) 2007-2008 by Guillaume Castagnino * casta at xwing dot info * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "kdcraw.h" #include "kdcraw_p.h" // Qt includes #include #include #include // KDE includes //#include // LibRaw includes #include #ifdef LIBRAW_HAS_CONFIG #include #endif // Local includes #include "libkdcraw_debug.h" #include "libkdcraw_version.h" #include "rawfiles.h" namespace KDcrawIface { KDcraw::KDcraw() : d(new Private(this)) { m_cancel = false; } KDcraw::~KDcraw() { cancel(); delete d; } QString KDcraw::version() { return QString(KDCRAW_VERSION_STRING); } void KDcraw::cancel() { m_cancel = true; } bool KDcraw::loadRawPreview(QImage& image, const QString& path) { // In first, try to extract the embedded JPEG preview. Very fast. bool ret = loadEmbeddedPreview(image, path); if (ret) return true; // In second, decode and half size of RAW picture. More slow. return (loadHalfPreview(image, path)); } bool KDcraw::loadEmbeddedPreview(QImage& image, const QString& path) { QByteArray imgData; if ( loadEmbeddedPreview(imgData, path) ) { qCDebug(LIBKDCRAW_LOG) << "Preview data size:" << imgData.size(); if (image.loadFromData( imgData )) { qCDebug(LIBKDCRAW_LOG) << "Using embedded RAW preview extraction"; return true; } } qCDebug(LIBKDCRAW_LOG) << "Failed to load embedded RAW preview"; return false; } bool KDcraw::loadEmbeddedPreview(QByteArray& imgData, const QString& path) { QFileInfo fileInfo(path); QString rawFilesExt(rawFiles()); QString ext = fileInfo.suffix().toUpper(); if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext)) return false; LibRaw raw; int ret = raw.open_file((const char*)(QFile::encodeName(path)).constData()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run open_file: " << libraw_strerror(ret); raw.recycle(); return false; } return (Private::loadEmbeddedPreview(imgData, raw)); } bool KDcraw::loadEmbeddedPreview(QByteArray& imgData, const QBuffer& buffer) { QString rawFilesExt(KDcrawIface::KDcraw::rawFiles()); LibRaw raw; QByteArray inData = buffer.data(); int ret = raw.open_buffer((void*) inData.data(), (size_t) inData.size()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run open_buffer: " << libraw_strerror(ret); raw.recycle(); return false; } return (Private::loadEmbeddedPreview(imgData, raw)); } bool KDcraw::loadHalfPreview(QImage& image, const QString& path) { QFileInfo fileInfo(path); QString rawFilesExt(rawFiles()); QString ext = fileInfo.suffix().toUpper(); if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext)) return false; qCDebug(LIBKDCRAW_LOG) << "Try to use reduced RAW picture extraction"; LibRaw raw; raw.imgdata.params.use_auto_wb = 1; // Use automatic white balance. raw.imgdata.params.use_camera_wb = 1; // Use camera white balance, if possible. raw.imgdata.params.half_size = 1; // Half-size color image (3x faster than -q). int ret = raw.open_file((const char*)(QFile::encodeName(path)).constData()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run open_file: " << libraw_strerror(ret); raw.recycle(); return false; } if(!Private::loadHalfPreview(image, raw)) { qCDebug(LIBKDCRAW_LOG) << "Failed to get half preview from LibRaw!"; return false; } qCDebug(LIBKDCRAW_LOG) << "Using reduced RAW picture extraction"; return true; } bool KDcraw::loadHalfPreview(QByteArray& imgData, const QString& path) { QFileInfo fileInfo(path); QString rawFilesExt(rawFiles()); QString ext = fileInfo.suffix().toUpper(); if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext)) return false; qCDebug(LIBKDCRAW_LOG) << "Try to use reduced RAW picture extraction"; LibRaw raw; int ret = raw.open_file((const char*)(QFile::encodeName(path)).constData()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run dcraw_process: " << libraw_strerror(ret); raw.recycle(); return false; } QImage image; if (!Private::loadHalfPreview(image, raw)) { qCDebug(LIBKDCRAW_LOG) << "KDcraw: failed to get half preview: " << libraw_strerror(ret); return false; } QBuffer buffer(&imgData); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "JPEG"); return true; } bool KDcraw::loadHalfPreview(QByteArray& imgData, const QBuffer& inBuffer) { QString rawFilesExt(KDcrawIface::KDcraw::rawFiles()); LibRaw raw; QByteArray inData = inBuffer.data(); int ret = raw.open_buffer((void*) inData.data(), (size_t) inData.size()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run dcraw_make_mem_image: " << libraw_strerror(ret); raw.recycle(); return false; } QImage image; if (!Private::loadHalfPreview(image, raw)) { qCDebug(LIBKDCRAW_LOG) << "KDcraw: failed to get half preview: " << libraw_strerror(ret); return false; } QBuffer buffer(&imgData); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "JPG"); return true; } bool KDcraw::loadFullImage(QImage& image, const QString& path, const RawDecodingSettings& settings) { QFileInfo fileInfo(path); QString rawFilesExt(rawFiles()); QString ext = fileInfo.suffix().toUpper(); if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext)) return false; qCDebug(LIBKDCRAW_LOG) << "Try to load full RAW picture..."; RawDecodingSettings prm = settings; prm.sixteenBitsImage = false; QByteArray imgData; int width, height, rgbmax; KDcraw decoder; bool ret = decoder.decodeRAWImage(path, prm, imgData, width, height, rgbmax); if (!ret) { qCDebug(LIBKDCRAW_LOG) << "Failled to load full RAW picture"; return false; } uchar* sptr = (uchar*)imgData.data(); uchar tmp8[2]; // Set RGB color components. for (int i = 0 ; i < width * height ; ++i) { // Swap Red and Blue tmp8[0] = sptr[2]; tmp8[1] = sptr[0]; sptr[0] = tmp8[0]; sptr[2] = tmp8[1]; sptr += 3; } image = QImage(width, height, QImage::Format_ARGB32); uint* dptr = reinterpret_cast(image.bits()); sptr = (uchar*)imgData.data(); for (int i = 0 ; i < width * height ; ++i) { *dptr++ = qRgba(sptr[2], sptr[1], sptr[0], 0xFF); sptr += 3; } qCDebug(LIBKDCRAW_LOG) << "Load full RAW picture done"; return true; } bool KDcraw::rawFileIdentify(DcrawInfoContainer& identify, const QString& path) { QFileInfo fileInfo(path); QString rawFilesExt(rawFiles()); QString ext = fileInfo.suffix().toUpper(); identify.isDecodable = false; if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext)) return false; LibRaw raw; int ret = raw.open_file((const char*)(QFile::encodeName(path)).constData()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run open_file: " << libraw_strerror(ret); raw.recycle(); return false; } ret = raw.adjust_sizes_info_only(); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run adjust_sizes_info_only: " << libraw_strerror(ret); raw.recycle(); return false; } Private::fillIndentifyInfo(&raw, identify); raw.recycle(); return true; } // ---------------------------------------------------------------------------------- bool KDcraw::extractRAWData(const QString& filePath, QByteArray& rawData, DcrawInfoContainer& identify, unsigned int shotSelect) { QFileInfo fileInfo(filePath); QString rawFilesExt(rawFiles()); QString ext = fileInfo.suffix().toUpper(); identify.isDecodable = false; if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext)) return false; if (m_cancel) return false; d->setProgress(0.1); LibRaw raw; // Set progress call back function. raw.set_progress_handler(callbackForLibRaw, d); int ret = raw.open_file((const char*)(QFile::encodeName(filePath)).constData()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run open_file: " << libraw_strerror(ret); raw.recycle(); return false; } if (m_cancel) { raw.recycle(); return false; } d->setProgress(0.3); raw.imgdata.params.output_bps = 16; raw.imgdata.params.shot_select = shotSelect; ret = raw.unpack(); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run unpack: " << libraw_strerror(ret); raw.recycle(); return false; } if (m_cancel) { raw.recycle(); return false; } d->setProgress(0.4); ret = raw.raw2image(); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run raw2image: " << libraw_strerror(ret); raw.recycle(); return false; } if (m_cancel) { raw.recycle(); return false; } d->setProgress(0.6); Private::fillIndentifyInfo(&raw, identify); if (m_cancel) { raw.recycle(); return false; } d->setProgress(0.8); rawData = QByteArray(); if (raw.imgdata.idata.filters == 0) { rawData.resize((int)(raw.imgdata.sizes.iwidth * raw.imgdata.sizes.iheight * raw.imgdata.idata.colors * sizeof(unsigned short))); unsigned short* output = reinterpret_cast(rawData.data()); for (unsigned int row = 0; row < raw.imgdata.sizes.iheight; row++) { for (unsigned int col = 0; col < raw.imgdata.sizes.iwidth; col++) { for (int color = 0; color < raw.imgdata.idata.colors; color++) { *output = raw.imgdata.image[raw.imgdata.sizes.iwidth*row + col][color]; output++; } } } } else { int w = raw.imgdata.sizes.iwidth; int h = raw.imgdata.sizes.iheight; rawData.resize(w * h * sizeof(unsigned short)); unsigned short* output = reinterpret_cast(rawData.data()); for (uint row = 0; row < raw.imgdata.sizes.iheight; row++) { for (uint col = 0; col < raw.imgdata.sizes.iwidth; col++) { *output = raw.imgdata.image[raw.imgdata.sizes.iwidth*row + col][raw.COLOR(row, col)]; output++; } } } raw.recycle(); d->setProgress(1.0); return true; } bool KDcraw::decodeHalfRAWImage(const QString& filePath, const RawDecodingSettings& rawDecodingSettings, QByteArray& imageData, int& width, int& height, int& rgbmax) { m_rawDecodingSettings = rawDecodingSettings; m_rawDecodingSettings.halfSizeColorImage = true; return (d->loadFromLibraw(filePath, imageData, width, height, rgbmax)); } bool KDcraw::decodeRAWImage(const QString& filePath, const RawDecodingSettings& rawDecodingSettings, QByteArray& imageData, int& width, int& height, int& rgbmax) { m_rawDecodingSettings = rawDecodingSettings; return (d->loadFromLibraw(filePath, imageData, width, height, rgbmax)); } bool KDcraw::checkToCancelWaitingData() { return m_cancel; } void KDcraw::setWaitingDataProgress(double) { } const char* KDcraw::rawFiles() { return raw_file_extentions; } QStringList KDcraw::rawFilesList() { QString string = QString::fromLatin1(rawFiles()); return string.remove("*.").split(' '); } int KDcraw::rawFilesVersion() { return raw_file_extensions_version; } QStringList KDcraw::supportedCamera() { QStringList camera; const char** const list = LibRaw::cameraList(); for (int i = 0; i < LibRaw::cameraCount(); i++) camera.append(list[i]); return camera; } QString KDcraw::librawVersion() { return QString(LIBRAW_VERSION_STR).remove("-Release"); } int KDcraw::librawUseGomp() { #ifdef LIBRAW_HAS_CONFIG # ifdef LIBRAW_USE_OPENMP return true; # else return false; # endif #else return -1; #endif } int KDcraw::librawUseRawSpeed() { #ifdef LIBRAW_HAS_CONFIG # ifdef LIBRAW_USE_RAWSPEED return true; # else return false; # endif #else return -1; #endif } int KDcraw::librawUseGPL2DemosaicPack() { #ifdef LIBRAW_HAS_CONFIG # ifdef LIBRAW_USE_DEMOSAIC_PACK_GPL2 return true; # else return false; # endif #else return -1; #endif } int KDcraw::librawUseGPL3DemosaicPack() { #ifdef LIBRAW_HAS_CONFIG # ifdef LIBRAW_USE_DEMOSAIC_PACK_GPL3 return true; # else return false; # endif #else return -1; #endif } } // namespace KDcrawIface diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.h b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.h index 61f7471f5b..549ad9158a 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.h +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.h @@ -1,267 +1,267 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2006-12-09 * @brief a tread-safe libraw C++ program interface * * @author Copyright (C) 2006-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2006-2013 by Marcel Wiesweg * marcel dot wiesweg at gmx dot de * @author Copyright (C) 2007-2008 by Guillaume Castagnino * casta at xwing dot info * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef KDCRAW_H #define KDCRAW_H // C++ includes #include // Qt includes #include #include #include #include // Local includes #include "rawdecodingsettings.h" #include "dcrawinfocontainer.h" /** @brief Main namespace of libKDcraw */ namespace KDcrawIface { class KDcraw : public QObject { Q_OBJECT public: /** Standard constructor. */ KDcraw(); /** Standard destructor. */ ~KDcraw() override; public: /** Return a string version of libkdcraw release */ static QString version(); /** Get the preview of RAW picture as a QImage. It tries loadEmbeddedPreview() first and if it fails, calls loadHalfPreview(). */ static bool loadRawPreview(QImage& image, const QString& path); /** Get the preview of RAW picture as a QByteArray holding JPEG data. It tries loadEmbeddedPreview() first and if it fails, calls loadHalfPreview(). */ static bool loadRawPreview(QByteArray& imgData, const QString& path); /** Get the preview of RAW picture passed in QBuffer as a QByteArray holding JPEG data. It tries loadEmbeddedPreview() first and if it fails, calls loadHalfPreview(). */ static bool loadRawPreview(QByteArray& imgData, const QBuffer& inBuffer); /** Get the embedded JPEG preview image from RAW picture as a QByteArray which will include Exif Data. This is fast and non cancelable. This method does not require a class instance to run. */ static bool loadEmbeddedPreview(QByteArray& imgData, const QString& path); /** Get the embedded JPEG preview image from RAW picture as a QImage. This is fast and non cancelable This method does not require a class instance to run. */ static bool loadEmbeddedPreview(QImage& image, const QString& path); /** Get the embedded JPEG preview image from RAW image passed in QBuffer as a QByteArray which will include Exif Data. This is fast and non cancelable. This method does not require a class instance to run. */ static bool loadEmbeddedPreview(QByteArray& imgData, const QBuffer& inBuffer); /** Get the half decoded RAW picture. This is slower than loadEmbeddedPreview() method and non cancelable. This method does not require a class instance to run. */ static bool loadHalfPreview(QImage& image, const QString& path); /** Get the half decoded RAW picture as JPEG data in QByteArray. This is slower than loadEmbeddedPreview() method and non cancelable. This method does not require a class instance to run. */ static bool loadHalfPreview(QByteArray& imgData, const QString& path); /** Get the half decoded RAW picture passed in QBuffer as JPEG data in QByteArray. This is slower than loadEmbeddedPreview() method and non cancelable. This method does not require a class instance to run. */ static bool loadHalfPreview(QByteArray& imgData, const QBuffer& inBuffer); /** Get the full decoded RAW picture. This is a more slower than loadHalfPreview() method and non cancelable. This method does not require a class instance to run. */ static bool loadFullImage(QImage& image, const QString& path, const RawDecodingSettings& settings = RawDecodingSettings()); /** Get the camera settings witch have taken RAW file. Look into dcrawinfocontainer.h for more details. This is a fast and non cancelable method witch do not require a class instance to run. */ static bool rawFileIdentify(DcrawInfoContainer& identify, const QString& path); /** Return the string of all RAW file type mime supported. */ static const char* rawFiles(); /** Return the list of all RAW file type mime supported, as a QStringList, without wildcard and suffix dot. */ static QStringList rawFilesList(); /** Returns a version number for the list of supported RAW file types. This version is incremented if the list of supported formats has changed between library releases. */ static int rawFilesVersion(); /** Provide a list of supported RAW Camera name. */ static QStringList supportedCamera(); /** Return LibRaw version string. */ static QString librawVersion(); /** Return true or false if LibRaw use parallel demosaicing or not (libgomp support). * Return -1 if undefined. */ static int librawUseGomp(); /** Return true or false if LibRaw use RawSpeed codec or not. * Return -1 if undefined. */ static int librawUseRawSpeed(); /** Return true or false if LibRaw use Demosaic Pack GPL2 or not. * Return -1 if undefined. */ static int librawUseGPL2DemosaicPack(); /** Return true or false if LibRaw use Demosaic Pack GPL3 or not. * Return -1 if undefined. */ static int librawUseGPL3DemosaicPack(); public: /** Extract Raw image data undemosaiced and without post processing from 'filePath' picture file. This is a cancelable method which require a class instance to run because RAW pictures loading can take a while. This method return: - A byte array container 'rawData' with raw data. - All info about Raw image into 'identify' container. - 'false' is returned if loadding failed, else 'true'. */ bool extractRAWData(const QString& filePath, QByteArray& rawData, DcrawInfoContainer& identify, unsigned int shotSelect=0); /** Extract a small size of decode RAW data from 'filePath' picture file using 'rawDecodingSettings' settings. This is a cancelable method which require a class instance to run because RAW pictures decoding can take a while. This method return: - A byte array container 'imageData' with picture data. Pixels order is RGB. Color depth can be 8 or 16. In 8 bits you can access to color component using (uchar*), in 16 bits using (ushort*). - Size size of image in number of pixels ('width' and 'height'). - The max average of RGB components from decoded picture. - 'false' is returned if decoding failed, else 'true'. */ bool decodeHalfRAWImage(const QString& filePath, const RawDecodingSettings& rawDecodingSettings, QByteArray& imageData, int& width, int& height, int& rgbmax); /** Extract a full size of RAW data from 'filePath' picture file using 'rawDecodingSettings' settings. This is a cancelable method which require a class instance to run because RAW pictures decoding can take a while. This method return: - A byte array container 'imageData' with picture data. Pixels order is RGB. Color depth can be 8 or 16. In 8 bits you can access to color component using (uchar*), in 16 bits using (ushort*). - Size size of image in number of pixels ('width' and 'height'). - The max average of RGB components from decoded picture. - 'false' is returned if decoding failed, else 'true'. */ bool decodeRAWImage(const QString& filePath, const RawDecodingSettings& rawDecodingSettings, QByteArray& imageData, int& width, int& height, int& rgbmax); /** To cancel 'decodeHalfRAWImage' and 'decodeRAWImage' methods running in a separate thread. */ void cancel(); protected: /** Used internally to cancel RAW decoding operation. Normally, you don't need to use it directly, excepted if you derivated this class. Usual way is to use cancel() method */ bool m_cancel; /** The settings container used to perform RAW pictures decoding. See 'rawdecodingsetting.h' for details. */ RawDecodingSettings m_rawDecodingSettings; protected: /** Re-implement this method to control the cancelisation of loop witch wait data from RAW decoding process with your propers envirronement. By default, this method check if m_cancel is true. */ virtual bool checkToCancelWaitingData(); /** Re-implement this method to control the pseudo progress value during RAW decoding (when dcraw run with an internal loop without feedback) with your proper environment. By default, this method does nothing. Progress value average for this stage is 0%-n%, with 'n' == 40% max (see setWaitingDataProgress() method). */ virtual void setWaitingDataProgress(double value); public: // Declared public to be called externally by callbackForLibRaw() static method. class Private; private: Private* const d; friend class Private; }; } // namespace KDcrawIface #endif /* KDCRAW_H */ diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw_p.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw_p.cpp index 29553fce35..6be94077f0 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw_p.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw_p.cpp @@ -1,698 +1,698 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2008-10-09 * @brief internal private container for KDcraw * * @author Copyright (C) 2008-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "kdcraw.h" #include "kdcraw_p.h" // Qt includes #include #include // Local includes #include "libkdcraw_debug.h" namespace KDcrawIface { int callbackForLibRaw(void* data, enum LibRaw_progress p, int iteration, int expected) { if (data) { KDcraw::Private* const d = static_cast(data); if (d) { return d->progressCallback(p, iteration, expected); } } return 0; } // -------------------------------------------------------------------------------------------------- KDcraw::Private::Private(KDcraw* const p) { m_progress = 0.0; m_parent = p; } KDcraw::Private::~Private() { } void KDcraw::Private::createPPMHeader(QByteArray& imgData, libraw_processed_image_t* const img) { QString header = QString("P%1\n%2 %3\n%4\n").arg(img->colors == 3 ? "6" : "5") .arg(img->width) .arg(img->height) .arg((1 << img->bits)-1); imgData.append(header.toLatin1()); imgData.append(QByteArray((const char*)img->data, (int)img->data_size)); } int KDcraw::Private::progressCallback(enum LibRaw_progress p, int iteration, int expected) { qCDebug(LIBKDCRAW_LOG) << "LibRaw progress: " << libraw_strprogress(p) << " pass " << iteration << " of " << expected; // post a little change in progress indicator to show raw processor activity. setProgress(progressValue()+0.01); // Clean processing termination by user... if (m_parent->checkToCancelWaitingData()) { qCDebug(LIBKDCRAW_LOG) << "LibRaw process terminaison invoked..."; m_parent->m_cancel = true; m_progress = 0.0; return 1; } // Return 0 to continue processing... return 0; } void KDcraw::Private::setProgress(double value) { m_progress = value; m_parent->setWaitingDataProgress(m_progress); } double KDcraw::Private::progressValue() const { return m_progress; } void KDcraw::Private::fillIndentifyInfo(LibRaw* const raw, DcrawInfoContainer& identify) { identify.dateTime.setSecsSinceEpoch(raw->imgdata.other.timestamp); identify.make = QString(raw->imgdata.idata.make); identify.model = QString(raw->imgdata.idata.model); identify.owner = QString(raw->imgdata.other.artist); identify.DNGVersion = QString::number(raw->imgdata.idata.dng_version); identify.sensitivity = raw->imgdata.other.iso_speed; identify.exposureTime = raw->imgdata.other.shutter; identify.aperture = raw->imgdata.other.aperture; identify.focalLength = raw->imgdata.other.focal_len; identify.imageSize = QSize(raw->imgdata.sizes.width, raw->imgdata.sizes.height); identify.fullSize = QSize(raw->imgdata.sizes.raw_width, raw->imgdata.sizes.raw_height); identify.outputSize = QSize(raw->imgdata.sizes.iwidth, raw->imgdata.sizes.iheight); identify.thumbSize = QSize(raw->imgdata.thumbnail.twidth, raw->imgdata.thumbnail.theight); identify.topMargin = raw->imgdata.sizes.top_margin; identify.leftMargin = raw->imgdata.sizes.left_margin; identify.hasIccProfile = raw->imgdata.color.profile ? true : false; identify.isDecodable = true; identify.pixelAspectRatio = raw->imgdata.sizes.pixel_aspect; identify.rawColors = raw->imgdata.idata.colors; identify.rawImages = raw->imgdata.idata.raw_count; identify.blackPoint = raw->imgdata.color.black; for (int ch = 0; ch < 4; ch++) { identify.blackPointCh[ch] = raw->imgdata.color.cblack[ch]; } identify.whitePoint = raw->imgdata.color.maximum; identify.orientation = (DcrawInfoContainer::ImageOrientation)raw->imgdata.sizes.flip; memcpy(&identify.cameraColorMatrix1, &raw->imgdata.color.cmatrix, sizeof(raw->imgdata.color.cmatrix)); memcpy(&identify.cameraColorMatrix2, &raw->imgdata.color.rgb_cam, sizeof(raw->imgdata.color.rgb_cam)); memcpy(&identify.cameraXYZMatrix, &raw->imgdata.color.cam_xyz, sizeof(raw->imgdata.color.cam_xyz)); if (raw->imgdata.idata.filters) { if (!raw->imgdata.idata.cdesc[3]) { raw->imgdata.idata.cdesc[3] = 'G'; } for (int i=0; i < 16; i++) { identify.filterPattern.append(raw->imgdata.idata.cdesc[raw->COLOR(i >> 1,i & 1)]); } identify.colorKeys = raw->imgdata.idata.cdesc; } for(int c = 0 ; c < raw->imgdata.idata.colors ; c++) { identify.daylightMult[c] = raw->imgdata.color.pre_mul[c]; } if (raw->imgdata.color.cam_mul[0] > 0) { for(int c = 0 ; c < 4 ; c++) { identify.cameraMult[c] = raw->imgdata.color.cam_mul[c]; } } } bool KDcraw::Private::loadFromLibraw(const QString& filePath, QByteArray& imageData, int& width, int& height, int& rgbmax) { m_parent->m_cancel = false; LibRaw raw; // Set progress call back function. raw.set_progress_handler(callbackForLibRaw, this); QByteArray deadpixelPath = QFile::encodeName(m_parent->m_rawDecodingSettings.deadPixelMap); QByteArray cameraProfile = QFile::encodeName(m_parent->m_rawDecodingSettings.inputProfile); QByteArray outputProfile = QFile::encodeName(m_parent->m_rawDecodingSettings.outputProfile); if (!m_parent->m_rawDecodingSettings.autoBrightness) { // Use a fixed white level, ignoring the image histogram. raw.imgdata.params.no_auto_bright = 1; } if (m_parent->m_rawDecodingSettings.sixteenBitsImage) { // (-4) 16bit ppm output raw.imgdata.params.output_bps = 16; } if (m_parent->m_rawDecodingSettings.halfSizeColorImage) { // (-h) Half-size color image (3x faster than -q). raw.imgdata.params.half_size = 1; } if (m_parent->m_rawDecodingSettings.RGBInterpolate4Colors) { // (-f) Interpolate RGB as four colors. raw.imgdata.params.four_color_rgb = 1; } if (m_parent->m_rawDecodingSettings.DontStretchPixels) { // (-j) Do not stretch the image to its correct aspect ratio. raw.imgdata.params.use_fuji_rotate = 1; } // (-H) Unclip highlight color. raw.imgdata.params.highlight = m_parent->m_rawDecodingSettings.unclipColors; if (m_parent->m_rawDecodingSettings.brightness != 1.0) { // (-b) Set Brightness value. raw.imgdata.params.bright = m_parent->m_rawDecodingSettings.brightness; } if (m_parent->m_rawDecodingSettings.enableBlackPoint) { // (-k) Set Black Point value. raw.imgdata.params.user_black = m_parent->m_rawDecodingSettings.blackPoint; } if (m_parent->m_rawDecodingSettings.enableWhitePoint) { // (-S) Set White Point value (saturation). raw.imgdata.params.user_sat = m_parent->m_rawDecodingSettings.whitePoint; } if (m_parent->m_rawDecodingSettings.medianFilterPasses > 0) { // (-m) After interpolation, clean up color artifacts by repeatedly applying a 3x3 median filter to the R-G and B-G channels. raw.imgdata.params.med_passes = m_parent->m_rawDecodingSettings.medianFilterPasses; } if (!m_parent->m_rawDecodingSettings.deadPixelMap.isEmpty()) { // (-P) Read the dead pixel list from this file. raw.imgdata.params.bad_pixels = deadpixelPath.data(); } switch (m_parent->m_rawDecodingSettings.whiteBalance) { case RawDecodingSettings::NONE: { break; } case RawDecodingSettings::CAMERA: { // (-w) Use camera white balance, if possible. raw.imgdata.params.use_camera_wb = 1; break; } case RawDecodingSettings::AUTO: { // (-a) Use automatic white balance. raw.imgdata.params.use_auto_wb = 1; break; } case RawDecodingSettings::CUSTOM: { /* Convert between Temperature and RGB. */ double T; double RGB[3]; double xD, yD, X, Y, Z; DcrawInfoContainer identify; T = m_parent->m_rawDecodingSettings.customWhiteBalance; /* Here starts the code picked and adapted from ufraw (0.12.1) to convert Temperature + green multiplier to RGB multipliers */ /* Convert between Temperature and RGB. * Base on information from http://www.brucelindbloom.com/ * The fit for D-illuminant between 4000K and 12000K are from CIE * The generalization to 2000K < T < 4000K and the blackbody fits * are my own and should be taken with a grain of salt. */ const double XYZ_to_RGB[3][3] = { { 3.24071, -0.969258, 0.0556352 }, {-1.53726, 1.87599, -0.203996 }, {-0.498571, 0.0415557, 1.05707 } }; // Fit for CIE Daylight illuminant if (T <= 4000) { xD = 0.27475e9/(T*T*T) - 0.98598e6/(T*T) + 1.17444e3/T + 0.145986; } else if (T <= 7000) { xD = -4.6070e9/(T*T*T) + 2.9678e6/(T*T) + 0.09911e3/T + 0.244063; } else { xD = -2.0064e9/(T*T*T) + 1.9018e6/(T*T) + 0.24748e3/T + 0.237040; } yD = -3*xD*xD + 2.87*xD - 0.275; X = xD/yD; Y = 1; Z = (1-xD-yD)/yD; RGB[0] = X*XYZ_to_RGB[0][0] + Y*XYZ_to_RGB[1][0] + Z*XYZ_to_RGB[2][0]; RGB[1] = X*XYZ_to_RGB[0][1] + Y*XYZ_to_RGB[1][1] + Z*XYZ_to_RGB[2][1]; RGB[2] = X*XYZ_to_RGB[0][2] + Y*XYZ_to_RGB[1][2] + Z*XYZ_to_RGB[2][2]; /* End of the code picked to ufraw */ RGB[1] = RGB[1] / m_parent->m_rawDecodingSettings.customWhiteBalanceGreen; /* By default, decraw override his default D65 WB We need to keep it as a basis : if not, colors with some DSLR will have a high dominant of color that will lead to a completely wrong WB */ if (rawFileIdentify(identify, filePath)) { RGB[0] = identify.daylightMult[0] / RGB[0]; RGB[1] = identify.daylightMult[1] / RGB[1]; RGB[2] = identify.daylightMult[2] / RGB[2]; } else { RGB[0] = 1.0 / RGB[0]; RGB[1] = 1.0 / RGB[1]; RGB[2] = 1.0 / RGB[2]; qCDebug(LIBKDCRAW_LOG) << "Warning: cannot get daylight multipliers"; } // (-r) set Raw Color Balance Multipliers. raw.imgdata.params.user_mul[0] = RGB[0]; raw.imgdata.params.user_mul[1] = RGB[1]; raw.imgdata.params.user_mul[2] = RGB[2]; raw.imgdata.params.user_mul[3] = RGB[1]; break; } case RawDecodingSettings::AERA: { // (-A) Calculate the white balance by averaging a rectangular area from image. raw.imgdata.params.greybox[0] = m_parent->m_rawDecodingSettings.whiteBalanceArea.left(); raw.imgdata.params.greybox[1] = m_parent->m_rawDecodingSettings.whiteBalanceArea.top(); raw.imgdata.params.greybox[2] = m_parent->m_rawDecodingSettings.whiteBalanceArea.width(); raw.imgdata.params.greybox[3] = m_parent->m_rawDecodingSettings.whiteBalanceArea.height(); break; } } // (-q) Use an interpolation method. raw.imgdata.params.user_qual = m_parent->m_rawDecodingSettings.RAWQuality; switch (m_parent->m_rawDecodingSettings.NRType) { case RawDecodingSettings::WAVELETSNR: { // (-n) Use wavelets to erase noise while preserving real detail. raw.imgdata.params.threshold = m_parent->m_rawDecodingSettings.NRThreshold; break; } case RawDecodingSettings::FBDDNR: { // (100 - 1000) => (1 - 10) conversion raw.imgdata.params.fbdd_noiserd = lround(m_parent->m_rawDecodingSettings.NRThreshold / 100.0); break; } #if !LIBRAW_COMPILE_CHECK_VERSION_NOTLESS(0, 19) case RawDecodingSettings::LINENR: { // (100 - 1000) => (0.001 - 0.02) conversion. raw.imgdata.params.linenoise = m_parent->m_rawDecodingSettings.NRThreshold * 2.11E-5 + 0.00111111; raw.imgdata.params.cfaline = true; break; } case RawDecodingSettings::IMPULSENR: { // (100 - 1000) => (0.005 - 0.05) conversion. raw.imgdata.params.lclean = m_parent->m_rawDecodingSettings.NRThreshold * 5E-5; raw.imgdata.params.cclean = m_parent->m_rawDecodingSettings.NRChroThreshold * 5E-5; raw.imgdata.params.cfa_clean = true; break; } #endif default: // No Noise Reduction { raw.imgdata.params.threshold = 0; raw.imgdata.params.fbdd_noiserd = 0; #if !LIBRAW_COMPILE_CHECK_VERSION_NOTLESS(0, 19) raw.imgdata.params.linenoise = 0; raw.imgdata.params.cfaline = false; raw.imgdata.params.lclean = 0; raw.imgdata.params.cclean = 0; raw.imgdata.params.cfa_clean = false; #endif break; } } #if !LIBRAW_COMPILE_CHECK_VERSION_NOTLESS(0, 19) // Chromatic aberration correction. raw.imgdata.params.ca_correc = m_parent->m_rawDecodingSettings.enableCACorrection; raw.imgdata.params.cared = m_parent->m_rawDecodingSettings.caMultiplier[0]; raw.imgdata.params.cablue = m_parent->m_rawDecodingSettings.caMultiplier[1]; #endif // Exposure Correction before interpolation. raw.imgdata.params.exp_correc = m_parent->m_rawDecodingSettings.expoCorrection; raw.imgdata.params.exp_shift = m_parent->m_rawDecodingSettings.expoCorrectionShift; raw.imgdata.params.exp_preser = m_parent->m_rawDecodingSettings.expoCorrectionHighlight; switch (m_parent->m_rawDecodingSettings.inputColorSpace) { case RawDecodingSettings::EMBEDDED: { // (-p embed) Use input profile from RAW file to define the camera's raw colorspace. raw.imgdata.params.camera_profile = (char*)"embed"; break; } case RawDecodingSettings::CUSTOMINPUTCS: { if (!m_parent->m_rawDecodingSettings.inputProfile.isEmpty()) { // (-p) Use input profile file to define the camera's raw colorspace. raw.imgdata.params.camera_profile = cameraProfile.data(); } break; } default: { // No input profile break; } } switch (m_parent->m_rawDecodingSettings.outputColorSpace) { case RawDecodingSettings::CUSTOMOUTPUTCS: { if (!m_parent->m_rawDecodingSettings.outputProfile.isEmpty()) { // (-o) Use ICC profile file to define the output colorspace. raw.imgdata.params.output_profile = outputProfile.data(); } break; } default: { // (-o) Define the output colorspace. raw.imgdata.params.output_color = m_parent->m_rawDecodingSettings.outputColorSpace; break; } } //-- Extended demosaicing settings ---------------------------------------------------------- raw.imgdata.params.dcb_iterations = m_parent->m_rawDecodingSettings.dcbIterations; raw.imgdata.params.dcb_enhance_fl = m_parent->m_rawDecodingSettings.dcbEnhanceFl; #if !LIBRAW_COMPILE_CHECK_VERSION_NOTLESS(0, 19) raw.imgdata.params.eeci_refine = m_parent->m_rawDecodingSettings.eeciRefine; raw.imgdata.params.es_med_passes = m_parent->m_rawDecodingSettings.esMedPasses; #endif //------------------------------------------------------------------------------------------- setProgress(0.1); qCDebug(LIBKDCRAW_LOG) << filePath; qCDebug(LIBKDCRAW_LOG) << m_parent->m_rawDecodingSettings; int ret = raw.open_file((const char*)(QFile::encodeName(filePath)).constData()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run open_file: " << libraw_strerror(ret); raw.recycle(); return false; } if (m_parent->m_cancel) { raw.recycle(); return false; } setProgress(0.2); ret = raw.unpack(); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run unpack: " << libraw_strerror(ret); raw.recycle(); return false; } if (m_parent->m_cancel) { raw.recycle(); return false; } setProgress(0.25); if (m_parent->m_rawDecodingSettings.fixColorsHighlights) { qCDebug(LIBKDCRAW_LOG) << "Applying LibRaw highlights adjustments"; // 1.0 is fallback to default value raw.imgdata.params.adjust_maximum_thr = 1.0; } else { qCDebug(LIBKDCRAW_LOG) << "Disabling LibRaw highlights adjustments"; // 0.0 disables this feature raw.imgdata.params.adjust_maximum_thr = 0.0; } ret = raw.dcraw_process(); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run dcraw_process: " << libraw_strerror(ret); raw.recycle(); return false; } if (m_parent->m_cancel) { raw.recycle(); return false; } setProgress(0.3); libraw_processed_image_t* img = raw.dcraw_make_mem_image(&ret); if(!img) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run dcraw_make_mem_image: " << libraw_strerror(ret); raw.recycle(); return false; } if (m_parent->m_cancel) { // Clear memory allocation. Introduced with LibRaw 0.11.0 raw.dcraw_clear_mem(img); raw.recycle(); return false; } setProgress(0.35); width = img->width; height = img->height; rgbmax = (1 << img->bits)-1; if (img->colors == 3) { imageData = QByteArray((const char*)img->data, (int)img->data_size); } else { // img->colors == 1 (Grayscale) : convert to RGB imageData = QByteArray(); for (int i = 0 ; i < (int)img->data_size ; ++i) { for (int j = 0 ; j < 3 ; ++j) { imageData.append(img->data[i]); } } } // Clear memory allocation. Introduced with LibRaw 0.11.0 raw.dcraw_clear_mem(img); raw.recycle(); if (m_parent->m_cancel) { return false; } setProgress(0.4); qCDebug(LIBKDCRAW_LOG) << "LibRaw: data info: width=" << width << " height=" << height << " rgbmax=" << rgbmax; return true; } bool KDcraw::Private::loadEmbeddedPreview(QByteArray& imgData, LibRaw& raw) { int ret = raw.unpack_thumb(); if (ret != LIBRAW_SUCCESS) { raw.recycle(); qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run unpack_thumb: " << libraw_strerror(ret); raw.recycle(); return false; } libraw_processed_image_t* const thumb = raw.dcraw_make_mem_thumb(&ret); if(!thumb) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run dcraw_make_mem_thumb: " << libraw_strerror(ret); raw.recycle(); return false; } if(thumb->type == LIBRAW_IMAGE_BITMAP) { createPPMHeader(imgData, thumb); } else { imgData = QByteArray((const char*)thumb->data, (int)thumb->data_size); } // Clear memory allocation. Introduced with LibRaw 0.11.0 raw.dcraw_clear_mem(thumb); raw.recycle(); if ( imgData.isEmpty() ) { qCDebug(LIBKDCRAW_LOG) << "Failed to load JPEG thumb from LibRaw!"; return false; } return true; } bool KDcraw::Private::loadHalfPreview(QImage& image, LibRaw& raw) { raw.imgdata.params.use_auto_wb = 1; // Use automatic white balance. raw.imgdata.params.use_camera_wb = 1; // Use camera white balance, if possible. raw.imgdata.params.half_size = 1; // Half-size color image (3x faster than -q). QByteArray imgData; int ret = raw.unpack(); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run unpack: " << libraw_strerror(ret); raw.recycle(); return false; } ret = raw.dcraw_process(); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run dcraw_process: " << libraw_strerror(ret); raw.recycle(); return false; } libraw_processed_image_t* halfImg = raw.dcraw_make_mem_image(&ret); if(!halfImg) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run dcraw_make_mem_image: " << libraw_strerror(ret); raw.recycle(); return false; } Private::createPPMHeader(imgData, halfImg); // Clear memory allocation. Introduced with LibRaw 0.11.0 raw.dcraw_clear_mem(halfImg); raw.recycle(); if ( imgData.isEmpty() ) { qCDebug(LIBKDCRAW_LOG) << "Failed to load half preview from LibRaw!"; return false; } if (!image.loadFromData(imgData)) { qCDebug(LIBKDCRAW_LOG) << "Failed to load PPM data from LibRaw!"; return false; } return true; } } // namespace KDcrawIface diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw_p.h b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw_p.h index 71b36b3e30..f233a18372 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw_p.h +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw_p.h @@ -1,109 +1,109 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2008-10-09 * @brief internal private container for KDcraw * * @author Copyright (C) 2008-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef KDCRAWPRIVATE_H #define KDCRAWPRIVATE_H // Qt includes #include // Pragma directives to reduce warnings from LibRaw header files. #if !defined(__APPLE__) && defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif #if defined(__APPLE__) && defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif // LibRaw includes #include // Restore warnings #if !defined(__APPLE__) && defined(__GNUC__) #pragma GCC diagnostic pop #endif #if defined(__APPLE__) && defined(__clang__) #pragma clang diagnostic pop #endif // Local includes #include "dcrawinfocontainer.h" #include "kdcraw.h" namespace KDcrawIface { extern "C" { int callbackForLibRaw(void* data, enum LibRaw_progress p, int iteration, int expected); } class Q_DECL_HIDDEN KDcraw::Private { public: Private(KDcraw* const p); ~Private(); public: int progressCallback(enum LibRaw_progress p, int iteration, int expected); void setProgress(double value); double progressValue() const; bool loadFromLibraw(const QString& filePath, QByteArray& imageData, int& width, int& height, int& rgbmax); public: static void createPPMHeader(QByteArray& imgData, libraw_processed_image_t* const img); static void fillIndentifyInfo(LibRaw* const raw, DcrawInfoContainer& identify); static bool loadEmbeddedPreview(QByteArray&, LibRaw&); static bool loadHalfPreview(QImage&, LibRaw&); private: double m_progress; KDcraw* m_parent; friend class KDcraw; }; } // namespace KDcrawIface #endif /* KDCRAWPRIVATE_H */ diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/ractionjob.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/ractionjob.cpp index 11f2d7033d..c8e88b2556 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/ractionjob.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/ractionjob.cpp @@ -1,51 +1,51 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2014-15-11 * @brief QRunnable job extended with QObject features * * @author Copyright (C) 2011-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2014 by Veaceslav Munteanu * veaceslav dot munteanu90 at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "ractionjob.h" namespace KDcrawIface { RActionJob::RActionJob() : QObject(), QRunnable(), m_cancel(false) { setAutoDelete(false); } RActionJob::~RActionJob() { cancel(); } void RActionJob::cancel() { m_cancel = true; } } // namespace KDcrawIface diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/ractionjob.h b/plugins/impex/raw/3rdparty/libkdcraw/src/ractionjob.h index 7dd60915c5..12a42e9629 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/ractionjob.h +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/ractionjob.h @@ -1,93 +1,93 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2014-15-11 * @brief QRunnable job extended with QObject features * * @author Copyright (C) 2011-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2014 by Veaceslav Munteanu * veaceslav dot munteanu90 at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef RACTIONJOB_H #define RACTIONJOB_H // Qt includes #include #include // Local includes namespace KDcrawIface { class RActionJob : public QObject, public QRunnable { Q_OBJECT public: /** Constructor which delegate deletion of QRunnable instance to RActionThreadBase, not QThreadPool. */ RActionJob(); /** Re-implement destructor in you implementation. Don't forget to cancel job. */ ~RActionJob() override; Q_SIGNALS: /** Use this signal in your implementation to inform RActionThreadBase manager that job is started */ void signalStarted(); /** Use this signal in your implementation to inform RActionThreadBase manager the job progress */ void signalProgress(int); /** Use this signal in your implementation to inform RActionThreadBase manager the job is done. */ void signalDone(); public Q_SLOTS: /** Call this method to cancel job. */ void cancel(); protected: /** You can use this boolean in your implementation to know if job must be canceled. */ bool m_cancel; }; /** Define a map of job/priority to process by RActionThreadBase manager. * Priority value can be used to control the run queue's order of execution. * Zero priority want mean to process job with higher priority. */ typedef QMap RJobCollection; } // namespace KDcrawIface #endif // RACTIONJOB_H diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/ractionthreadbase.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/ractionthreadbase.cpp index 4fad77e4bf..db8d610894 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/ractionthreadbase.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/ractionthreadbase.cpp @@ -1,202 +1,202 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2011-12-28 * @brief re-implementation of action thread using threadweaver * * @author Copyright (C) 2011-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2014 by Veaceslav Munteanu * veaceslav dot munteanu90 at gmail dot com * @author Copyright (C) 2011-2012 by A Janardhan Reddy * annapareddyjanardhanreddy at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "ractionthreadbase.h" // Qt includes #include #include #include #include #include #include // Local includes #include "libkdcraw_debug.h" namespace KDcrawIface { class Q_DECL_HIDDEN RActionThreadBase::Private { public: Private() { running = false; pool = QThreadPool::globalInstance(); } volatile bool running; QWaitCondition condVarJobs; QMutex mutex; RJobCollection todo; RJobCollection pending; RJobCollection processed; QThreadPool* pool; }; RActionThreadBase::RActionThreadBase(QObject* const parent) : QThread(parent), d(new Private) { defaultMaximumNumberOfThreads(); } RActionThreadBase::~RActionThreadBase() { // cancel the thread cancel(); // wait for the thread to finish wait(); // Cleanup all jobs from memory Q_FOREACH (RActionJob* const job, d->todo.keys()) delete(job); Q_FOREACH (RActionJob* const job, d->pending.keys()) delete(job); Q_FOREACH (RActionJob* const job, d->processed.keys()) delete(job); delete d; } void RActionThreadBase::setMaximumNumberOfThreads(int n) { d->pool->setMaxThreadCount(n); qCDebug(LIBKDCRAW_LOG) << "Using " << n << " CPU core to run threads"; } int RActionThreadBase::maximumNumberOfThreads() const { return d->pool->maxThreadCount(); } void RActionThreadBase::defaultMaximumNumberOfThreads() { const int maximumNumberOfThreads = qMax(QThreadPool::globalInstance()->maxThreadCount(), 1); setMaximumNumberOfThreads(maximumNumberOfThreads); } void RActionThreadBase::slotJobFinished() { RActionJob* const job = dynamic_cast(sender()); if (!job) return; qCDebug(LIBKDCRAW_LOG) << "One job is done"; QMutexLocker lock(&d->mutex); d->processed.insert(job, 0); d->pending.remove(job); if (isEmpty()) { d->running = false; } d->condVarJobs.wakeAll(); } void RActionThreadBase::cancel() { qCDebug(LIBKDCRAW_LOG) << "Cancel Main Thread"; QMutexLocker lock(&d->mutex); d->todo.clear(); Q_FOREACH (RActionJob* const job, d->pending.keys()) { job->cancel(); d->processed.insert(job, 0); } d->pending.clear(); d->condVarJobs.wakeAll(); d->running = false; } bool RActionThreadBase::isEmpty() const { return d->pending.isEmpty(); } void RActionThreadBase::appendJobs(const RJobCollection& jobs) { QMutexLocker lock(&d->mutex); for (RJobCollection::const_iterator it = jobs.begin() ; it != jobs.end(); ++it) { d->todo.insert(it.key(), it.value()); } d->condVarJobs.wakeAll(); } void RActionThreadBase::run() { d->running = true; while (d->running) { QMutexLocker lock(&d->mutex); if (!d->todo.isEmpty()) { qCDebug(LIBKDCRAW_LOG) << "Action Thread run " << d->todo.count() << " new jobs"; for (RJobCollection::iterator it = d->todo.begin() ; it != d->todo.end(); ++it) { RActionJob* const job = it.key(); int priority = it.value(); connect(job, SIGNAL(signalDone()), this, SLOT(slotJobFinished())); d->pool->start(job, priority); d->pending.insert(job, priority); } d->todo.clear(); } else { d->condVarJobs.wait(&d->mutex); } } } } // namespace KDcrawIface diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/ractionthreadbase.h b/plugins/impex/raw/3rdparty/libkdcraw/src/ractionthreadbase.h index 8f1f653615..ed96335359 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/ractionthreadbase.h +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/ractionthreadbase.h @@ -1,98 +1,98 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2011-12-28 * @brief re-implementation of action thread using threadweaver * * @author Copyright (C) 2011-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2014 by Veaceslav Munteanu * veaceslav dot munteanu90 at gmail dot com * @author Copyright (C) 2011-2012 by A Janardhan Reddy * annapareddyjanardhanreddy at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef RACTION_THREAD_BASE_H #define RACTION_THREAD_BASE_H // Qt includes #include // Local includes #include "ractionjob.h" namespace KDcrawIface { class RActionThreadBase : public QThread { Q_OBJECT public: RActionThreadBase(QObject* const parent=0); ~RActionThreadBase() override; /** Adjust maximum number of threads used to parallelize collection of job processing. */ void setMaximumNumberOfThreads(int n); /** Return the maximum number of threads used to parallelize collection of job processing. */ int maximumNumberOfThreads() const; /** Reset maximum number of threads used to parallelize collection of job processing to max core detected on computer. * This method is called in contructor. */ void defaultMaximumNumberOfThreads(); /** Cancel processing of current jobs under progress. */ void cancel(); protected: /** Main thread loop used to process jobs in todo list. */ void run() override; /** Append a collection of jobs to process into QThreadPool. * Jobs are add to pending lists and will be deleted by RActionThreadBase, not QThreadPool. */ void appendJobs(const RJobCollection& jobs); /** Return true if list of pending jobs to process is empty. */ bool isEmpty() const; protected Q_SLOTS: void slotJobFinished(); private: class Private; Private* const d; }; } // namespace KDcrawIface #endif // RACTION_THREAD_BASE_H diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rawdecodingsettings.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/rawdecodingsettings.cpp index 0736f12301..5a265d7908 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/rawdecodingsettings.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rawdecodingsettings.cpp @@ -1,396 +1,396 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2006-12-09 * @brief Raw decoding settings * * @author Copyright (C) 2006-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2006-2013 by Marcel Wiesweg * marcel dot wiesweg at gmx dot de * @author Copyright (C) 2007-2008 by Guillaume Castagnino * casta at xwing dot info * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #define OPTIONFIXCOLORSHIGHLIGHTSENTRY "FixColorsHighlights" #define OPTIONDECODESIXTEENBITENTRY "SixteenBitsImage" #define OPTIONWHITEBALANCEENTRY "White Balance" #define OPTIONCUSTOMWHITEBALANCEENTRY "Custom White Balance" #define OPTIONCUSTOMWBGREENENTRY "Custom White Balance Green" #define OPTIONFOURCOLORRGBENTRY "Four Color RGB" #define OPTIONUNCLIPCOLORSENTRY "Unclip Color" // Wrong spelling, but do not fix it since it is a configuration key // krazy:cond=spelling #define OPTIONDONTSTRETCHPIXELSENTRY "Dont Stretch Pixels" // krazy:endcond=spelling #define OPTIONMEDIANFILTERPASSESENTRY "Median Filter Passes" #define OPTIONNOISEREDUCTIONTYPEENTRY "Noise Reduction Type" #define OPTIONNOISEREDUCTIONTHRESHOLDENTRY "Noise Reduction Threshold" #define OPTIONUSECACORRECTIONENTRY "EnableCACorrection" #define OPTIONCAREDMULTIPLIERENTRY "caRedMultiplier" #define OPTIONCABLUEMULTIPLIERENTRY "caBlueMultiplier" #define OPTIONAUTOBRIGHTNESSENTRY "AutoBrightness" #define OPTIONDECODINGQUALITYENTRY "Decoding Quality" #define OPTIONINPUTCOLORSPACEENTRY "Input Color Space" #define OPTIONOUTPUTCOLORSPACEENTRY "Output Color Space" #define OPTIONINPUTCOLORPROFILEENTRY "Input Color Profile" #define OPTIONOUTPUTCOLORPROFILEENTRY "Output Color Profile" #define OPTIONBRIGHTNESSMULTIPLIERENTRY "Brightness Multiplier" #define OPTIONUSEBLACKPOINTENTRY "Use Black Point" #define OPTIONBLACKPOINTENTRY "Black Point" #define OPTIONUSEWHITEPOINTENTRY "Use White Point" #define OPTIONWHITEPOINTENTRY "White Point" //-- Extended demosaicing settings ---------------------------------------------------------- #define OPTIONDCBITERATIONSENTRY "Dcb Iterations" #define OPTIONDCBENHANCEFLENTRY "Dcb Enhance Filter" #define OPTIONEECIREFINEENTRY "Eeci Refine" #define OPTIONESMEDPASSESENTRY "Es Median Filter Passes" #define OPTIONNRCHROMINANCETHRESHOLDENTRY "Noise Reduction Chrominance Threshold" #define OPTIONEXPOCORRECTIONENTRY "Expo Correction" #define OPTIONEXPOCORRECTIONSHIFTENTRY "Expo Correction Shift" #define OPTIONEXPOCORRECTIONHIGHLIGHTENTRY "Expo Correction Highlight" #include "rawdecodingsettings.h" namespace KDcrawIface { RawDecodingSettings::RawDecodingSettings() { fixColorsHighlights = false; autoBrightness = true; sixteenBitsImage = false; brightness = 1.0; RAWQuality = BILINEAR; inputColorSpace = NOINPUTCS; outputColorSpace = SRGB; RGBInterpolate4Colors = false; DontStretchPixels = false; unclipColors = 0; whiteBalance = CAMERA; customWhiteBalance = 6500; customWhiteBalanceGreen = 1.0; medianFilterPasses = 0; halfSizeColorImage = false; enableBlackPoint = false; blackPoint = 0; enableWhitePoint = false; whitePoint = 0; NRType = NONR; NRThreshold = 0; enableCACorrection = false; caMultiplier[0] = 0.0; caMultiplier[1] = 0.0; inputProfile = QString(); outputProfile = QString(); deadPixelMap = QString(); whiteBalanceArea = QRect(); //-- Extended demosaicing settings ---------------------------------------------------------- dcbIterations = -1; dcbEnhanceFl = false; eeciRefine = false; esMedPasses = 0; NRChroThreshold = 0; expoCorrection = false; expoCorrectionShift = 1.0; expoCorrectionHighlight = 0.0; } RawDecodingSettings::~RawDecodingSettings() { } RawDecodingSettings& RawDecodingSettings::operator=(const RawDecodingSettings& o) { fixColorsHighlights = o.fixColorsHighlights; autoBrightness = o.autoBrightness; sixteenBitsImage = o.sixteenBitsImage; brightness = o.brightness; RAWQuality = o.RAWQuality; inputColorSpace = o.inputColorSpace; outputColorSpace = o.outputColorSpace; RGBInterpolate4Colors = o.RGBInterpolate4Colors; DontStretchPixels = o.DontStretchPixels; unclipColors = o.unclipColors; whiteBalance = o.whiteBalance; customWhiteBalance = o.customWhiteBalance; customWhiteBalanceGreen = o.customWhiteBalanceGreen; halfSizeColorImage = o.halfSizeColorImage; enableBlackPoint = o.enableBlackPoint; blackPoint = o.blackPoint; enableWhitePoint = o.enableWhitePoint; whitePoint = o.whitePoint; NRType = o.NRType; NRThreshold = o.NRThreshold; enableCACorrection = o.enableCACorrection; caMultiplier[0] = o.caMultiplier[0]; caMultiplier[1] = o.caMultiplier[1]; medianFilterPasses = o.medianFilterPasses; inputProfile = o.inputProfile; outputProfile = o.outputProfile; deadPixelMap = o.deadPixelMap; whiteBalanceArea = o.whiteBalanceArea; //-- Extended demosaicing settings ---------------------------------------------------------- dcbIterations = o.dcbIterations; dcbEnhanceFl = o.dcbEnhanceFl; eeciRefine = o.eeciRefine; esMedPasses = o.esMedPasses; NRChroThreshold = o.NRChroThreshold; expoCorrection = o.expoCorrection; expoCorrectionShift = o.expoCorrectionShift; expoCorrectionHighlight = o.expoCorrectionHighlight; return *this; } bool RawDecodingSettings::operator==(const RawDecodingSettings& o) const { return fixColorsHighlights == o.fixColorsHighlights && autoBrightness == o.autoBrightness && sixteenBitsImage == o.sixteenBitsImage && brightness == o.brightness && RAWQuality == o.RAWQuality && inputColorSpace == o.inputColorSpace && outputColorSpace == o.outputColorSpace && RGBInterpolate4Colors == o.RGBInterpolate4Colors && DontStretchPixels == o.DontStretchPixels && unclipColors == o.unclipColors && whiteBalance == o.whiteBalance && customWhiteBalance == o.customWhiteBalance && customWhiteBalanceGreen == o.customWhiteBalanceGreen && halfSizeColorImage == o.halfSizeColorImage && enableBlackPoint == o.enableBlackPoint && blackPoint == o.blackPoint && enableWhitePoint == o.enableWhitePoint && whitePoint == o.whitePoint && NRType == o.NRType && NRThreshold == o.NRThreshold && enableCACorrection == o.enableCACorrection && caMultiplier[0] == o.caMultiplier[0] && caMultiplier[1] == o.caMultiplier[1] && medianFilterPasses == o.medianFilterPasses && inputProfile == o.inputProfile && outputProfile == o.outputProfile && deadPixelMap == o.deadPixelMap && whiteBalanceArea == o.whiteBalanceArea //-- Extended demosaicing settings ---------------------------------------------------------- && dcbIterations == o.dcbIterations && dcbEnhanceFl == o.dcbEnhanceFl && eeciRefine == o.eeciRefine && esMedPasses == o.esMedPasses && NRChroThreshold == o.NRChroThreshold && expoCorrection == o.expoCorrection && expoCorrectionShift == o.expoCorrectionShift && expoCorrectionHighlight == o.expoCorrectionHighlight ; } void RawDecodingSettings::optimizeTimeLoading() { fixColorsHighlights = false; autoBrightness = true; sixteenBitsImage = true; brightness = 1.0; RAWQuality = BILINEAR; inputColorSpace = NOINPUTCS; outputColorSpace = SRGB; RGBInterpolate4Colors = false; DontStretchPixels = false; unclipColors = 0; whiteBalance = CAMERA; customWhiteBalance = 6500; customWhiteBalanceGreen = 1.0; halfSizeColorImage = true; medianFilterPasses = 0; enableBlackPoint = false; blackPoint = 0; enableWhitePoint = false; whitePoint = 0; NRType = NONR; NRThreshold = 0; enableCACorrection = false; caMultiplier[0] = 0.0; caMultiplier[1] = 0.0; inputProfile = QString(); outputProfile = QString(); deadPixelMap = QString(); whiteBalanceArea = QRect(); //-- Extended demosaicing settings ---------------------------------------------------------- dcbIterations = -1; dcbEnhanceFl = false; eeciRefine = false; esMedPasses = 0; NRChroThreshold = 0; expoCorrection = false; expoCorrectionShift = 1.0; expoCorrectionHighlight = 0.0; } void RawDecodingSettings::readSettings(KConfigGroup& group) { RawDecodingSettings defaultPrm; fixColorsHighlights = group.readEntry(OPTIONFIXCOLORSHIGHLIGHTSENTRY, defaultPrm.fixColorsHighlights); sixteenBitsImage = group.readEntry(OPTIONDECODESIXTEENBITENTRY, defaultPrm.sixteenBitsImage); whiteBalance = (WhiteBalance) group.readEntry(OPTIONWHITEBALANCEENTRY, (int)defaultPrm.whiteBalance); customWhiteBalance = group.readEntry(OPTIONCUSTOMWHITEBALANCEENTRY, defaultPrm.customWhiteBalance); customWhiteBalanceGreen = group.readEntry(OPTIONCUSTOMWBGREENENTRY, defaultPrm.customWhiteBalanceGreen); RGBInterpolate4Colors = group.readEntry(OPTIONFOURCOLORRGBENTRY, defaultPrm.RGBInterpolate4Colors); unclipColors = group.readEntry(OPTIONUNCLIPCOLORSENTRY, defaultPrm.unclipColors); DontStretchPixels = group.readEntry(OPTIONDONTSTRETCHPIXELSENTRY, defaultPrm.DontStretchPixels); NRType = (NoiseReduction) group.readEntry(OPTIONNOISEREDUCTIONTYPEENTRY, (int)defaultPrm.NRType); brightness = group.readEntry(OPTIONBRIGHTNESSMULTIPLIERENTRY, defaultPrm.brightness); enableBlackPoint = group.readEntry(OPTIONUSEBLACKPOINTENTRY, defaultPrm.enableBlackPoint); blackPoint = group.readEntry(OPTIONBLACKPOINTENTRY, defaultPrm.blackPoint); enableWhitePoint = group.readEntry(OPTIONUSEWHITEPOINTENTRY, defaultPrm.enableWhitePoint); whitePoint = group.readEntry(OPTIONWHITEPOINTENTRY, defaultPrm.whitePoint); medianFilterPasses = group.readEntry(OPTIONMEDIANFILTERPASSESENTRY, defaultPrm.medianFilterPasses); NRThreshold = group.readEntry(OPTIONNOISEREDUCTIONTHRESHOLDENTRY, defaultPrm.NRThreshold); enableCACorrection = group.readEntry(OPTIONUSECACORRECTIONENTRY, defaultPrm.enableCACorrection); caMultiplier[0] = group.readEntry(OPTIONCAREDMULTIPLIERENTRY, defaultPrm.caMultiplier[0]); caMultiplier[1] = group.readEntry(OPTIONCABLUEMULTIPLIERENTRY, defaultPrm.caMultiplier[1]); RAWQuality = (DecodingQuality) group.readEntry(OPTIONDECODINGQUALITYENTRY, (int)defaultPrm.RAWQuality); outputColorSpace = (OutputColorSpace) group.readEntry(OPTIONOUTPUTCOLORSPACEENTRY, (int)defaultPrm.outputColorSpace); autoBrightness = group.readEntry(OPTIONAUTOBRIGHTNESSENTRY, defaultPrm.autoBrightness); //-- Extended demosaicing settings ---------------------------------------------------------- dcbIterations = group.readEntry(OPTIONDCBITERATIONSENTRY, defaultPrm.dcbIterations); dcbEnhanceFl = group.readEntry(OPTIONDCBENHANCEFLENTRY, defaultPrm.dcbEnhanceFl); eeciRefine = group.readEntry(OPTIONEECIREFINEENTRY, defaultPrm.eeciRefine); esMedPasses = group.readEntry(OPTIONESMEDPASSESENTRY, defaultPrm.esMedPasses); NRChroThreshold = group.readEntry(OPTIONNRCHROMINANCETHRESHOLDENTRY, defaultPrm.NRChroThreshold); expoCorrection = group.readEntry(OPTIONEXPOCORRECTIONENTRY, defaultPrm.expoCorrection); expoCorrectionShift = group.readEntry(OPTIONEXPOCORRECTIONSHIFTENTRY, defaultPrm.expoCorrectionShift); expoCorrectionHighlight = group.readEntry(OPTIONEXPOCORRECTIONHIGHLIGHTENTRY, defaultPrm.expoCorrectionHighlight); } void RawDecodingSettings::writeSettings(KConfigGroup& group) { group.writeEntry(OPTIONFIXCOLORSHIGHLIGHTSENTRY, fixColorsHighlights); group.writeEntry(OPTIONDECODESIXTEENBITENTRY, sixteenBitsImage); group.writeEntry(OPTIONWHITEBALANCEENTRY, (int)whiteBalance); group.writeEntry(OPTIONCUSTOMWHITEBALANCEENTRY, customWhiteBalance); group.writeEntry(OPTIONCUSTOMWBGREENENTRY, customWhiteBalanceGreen); group.writeEntry(OPTIONFOURCOLORRGBENTRY, RGBInterpolate4Colors); group.writeEntry(OPTIONUNCLIPCOLORSENTRY, unclipColors); group.writeEntry(OPTIONDONTSTRETCHPIXELSENTRY, DontStretchPixels); group.writeEntry(OPTIONNOISEREDUCTIONTYPEENTRY, (int)NRType); group.writeEntry(OPTIONBRIGHTNESSMULTIPLIERENTRY, brightness); group.writeEntry(OPTIONUSEBLACKPOINTENTRY, enableBlackPoint); group.writeEntry(OPTIONBLACKPOINTENTRY, blackPoint); group.writeEntry(OPTIONUSEWHITEPOINTENTRY, enableWhitePoint); group.writeEntry(OPTIONWHITEPOINTENTRY, whitePoint); group.writeEntry(OPTIONMEDIANFILTERPASSESENTRY, medianFilterPasses); group.writeEntry(OPTIONNOISEREDUCTIONTHRESHOLDENTRY, NRThreshold); group.writeEntry(OPTIONUSECACORRECTIONENTRY, enableCACorrection); group.writeEntry(OPTIONCAREDMULTIPLIERENTRY, caMultiplier[0]); group.writeEntry(OPTIONCABLUEMULTIPLIERENTRY, caMultiplier[1]); group.writeEntry(OPTIONDECODINGQUALITYENTRY, (int)RAWQuality); group.writeEntry(OPTIONOUTPUTCOLORSPACEENTRY, (int)outputColorSpace); group.writeEntry(OPTIONAUTOBRIGHTNESSENTRY, autoBrightness); //-- Extended demosaicing settings ---------------------------------------------------------- group.writeEntry(OPTIONDCBITERATIONSENTRY, dcbIterations); group.writeEntry(OPTIONDCBENHANCEFLENTRY, dcbEnhanceFl); group.writeEntry(OPTIONEECIREFINEENTRY, eeciRefine); group.writeEntry(OPTIONESMEDPASSESENTRY, esMedPasses); group.writeEntry(OPTIONNRCHROMINANCETHRESHOLDENTRY, NRChroThreshold); group.writeEntry(OPTIONEXPOCORRECTIONENTRY, expoCorrection); group.writeEntry(OPTIONEXPOCORRECTIONSHIFTENTRY, expoCorrectionShift); group.writeEntry(OPTIONEXPOCORRECTIONHIGHLIGHTENTRY, expoCorrectionHighlight); } QDebug operator<<(QDebug dbg, const RawDecodingSettings& s) { dbg.nospace() << endl; dbg.nospace() << "-- RAW DECODING SETTINGS --------------------------------" << endl; dbg.nospace() << "-- autoBrightness: " << s.autoBrightness << endl; dbg.nospace() << "-- sixteenBitsImage: " << s.sixteenBitsImage << endl; dbg.nospace() << "-- brightness: " << s.brightness << endl; dbg.nospace() << "-- RAWQuality: " << s.RAWQuality << endl; dbg.nospace() << "-- inputColorSpace: " << s.inputColorSpace << endl; dbg.nospace() << "-- outputColorSpace: " << s.outputColorSpace << endl; dbg.nospace() << "-- RGBInterpolate4Colors: " << s.RGBInterpolate4Colors << endl; dbg.nospace() << "-- DontStretchPixels: " << s.DontStretchPixels << endl; dbg.nospace() << "-- unclipColors: " << s.unclipColors << endl; dbg.nospace() << "-- whiteBalance: " << s.whiteBalance << endl; dbg.nospace() << "-- customWhiteBalance: " << s.customWhiteBalance << endl; dbg.nospace() << "-- customWhiteBalanceGreen: " << s.customWhiteBalanceGreen << endl; dbg.nospace() << "-- halfSizeColorImage: " << s.halfSizeColorImage << endl; dbg.nospace() << "-- enableBlackPoint: " << s.enableBlackPoint << endl; dbg.nospace() << "-- blackPoint: " << s.blackPoint << endl; dbg.nospace() << "-- enableWhitePoint: " << s.enableWhitePoint << endl; dbg.nospace() << "-- whitePoint: " << s.whitePoint << endl; dbg.nospace() << "-- NoiseReductionType: " << s.NRType << endl; dbg.nospace() << "-- NoiseReductionThreshold: " << s.NRThreshold << endl; dbg.nospace() << "-- enableCACorrection: " << s.enableCACorrection << endl; dbg.nospace() << "-- caMultiplier: " << s.caMultiplier[0] << ", " << s.caMultiplier[1] << endl; dbg.nospace() << "-- medianFilterPasses: " << s.medianFilterPasses << endl; dbg.nospace() << "-- inputProfile: " << s.inputProfile << endl; dbg.nospace() << "-- outputProfile: " << s.outputProfile << endl; dbg.nospace() << "-- deadPixelMap: " << s.deadPixelMap << endl; dbg.nospace() << "-- whiteBalanceArea: " << s.whiteBalanceArea << endl; //-- Extended demosaicing settings ---------------------------------------------------------- dbg.nospace() << "-- dcbIterations: " << s.dcbIterations << endl; dbg.nospace() << "-- dcbEnhanceFl: " << s.dcbEnhanceFl << endl; dbg.nospace() << "-- eeciRefine: " << s.eeciRefine << endl; dbg.nospace() << "-- esMedPasses: " << s.esMedPasses << endl; dbg.nospace() << "-- NRChrominanceThreshold: " << s.NRChroThreshold << endl; dbg.nospace() << "-- expoCorrection: " << s.expoCorrection << endl; dbg.nospace() << "-- expoCorrectionShift: " << s.expoCorrectionShift << endl; dbg.nospace() << "-- expoCorrectionHighlight: " << s.expoCorrectionHighlight << endl; dbg.nospace() << "---------------------------------------------------------" << endl; return dbg.space(); } } // namespace KDcrawIface diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rawdecodingsettings.h b/plugins/impex/raw/3rdparty/libkdcraw/src/rawdecodingsettings.h index 5916fb2260..5927f4dd6b 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/rawdecodingsettings.h +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rawdecodingsettings.h @@ -1,376 +1,376 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2006-12-09 * @brief Raw decoding settings * * @author Copyright (C) 2006-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2006-2013 by Marcel Wiesweg * marcel dot wiesweg at gmx dot de * @author Copyright (C) 2007-2008 by Guillaume Castagnino * casta at xwing dot info * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef RAW_DECODING_SETTINGS_H #define RAW_DECODING_SETTINGS_H // Qt includes #include #include #include // KDE includes #include // Local includes namespace KDcrawIface { class RawDecodingSettings { public: /** RAW decoding Interpolation methods * * Bilinear: use high-speed but low-quality bilinear * interpolation (default - for slow computer). In this method, * the red value of a non-red pixel is computed as the average of * the adjacent red pixels, and similar for blue and green. * VNG: use Variable Number of Gradients interpolation. * This method computes gradients near the pixel of interest and uses * the lower gradients (representing smoother and more similar parts * of the image) to make an estimate. * PPG: use Patterned Pixel Grouping interpolation. * Pixel Grouping uses assumptions about natural scenery in making estimates. * It has fewer color artifacts on natural images than the Variable Number of * Gradients method. * AHD: use Adaptive Homogeneity-Directed interpolation. * This method selects the direction of interpolation so as to * maximize a homogeneity metric, thus typically minimizing color artifacts. * DCB: DCB interpolation (see http://www.linuxphoto.org/html/dcb.html for details) * * NOTE: from GPL2/GPL3 demosaic packs - will not work with libraw>=0.19 * - * PL_AHD: modified AHD interpolation (see http://sites.google.com/site/demosaicalgorithms/modified-dcraw + * PL_AHD: modified AHD interpolation (see https://sites.google.com/site/demosaicalgorithms/modified-dcraw * for details). * AFD: demosaicing through 5 pass median filter from PerfectRaw project. * VCD: VCD interpolation. * VCD_AHD: mixed demosaicing between VCD and AHD. * LMMSE: LMMSE interpolation from PerfectRaw. * AMAZE: AMaZE interpolation and color aberration removal from RawTherapee project. * * NOTE: for libraw>=0.19 only * * DHT: DHT interpolation. * AAHD: Enhanced Adaptative AHD interpolation. */ enum DecodingQuality { BILINEAR = 0, VNG = 1, PPG = 2, AHD = 3, DCB = 4, PL_AHD = 5, AFD = 6, VCD = 7, VCD_AHD = 8, LMMSE = 9, AMAZE = 10, DHT = 11, AAHD = 12 }; /** White balances alternatives * NONE: no white balance used : reverts to standard daylight D65 WB. * CAMERA: Use the camera embedded WB if available. Reverts to NONE if not. * AUTO: Averages an auto WB on the entire image. * CUSTOM: Let use set it's own temperature and green factor (later converted to RGBG factors). * AERA: Let use an aera from image to average white balance (see whiteBalanceArea for details). */ enum WhiteBalance { NONE = 0, CAMERA = 1, AUTO = 2, CUSTOM = 3, AERA = 4 }; /** Noise Reduction method to apply before demosaicing * NONR: No noise reduction. * WAVELETSNR: wavelets correction to erase noise while preserving real detail. It's applied after interpolation. * FBDDNR: Fake Before Demosaicing Denoising noise reduction. It's applied before interpolation. * LINENR: CFA Line Denoise. It's applied after interpolation. * IMPULSENR: Impulse Denoise. It's applied after interpolation. */ enum NoiseReduction { NONR = 0, WAVELETSNR, FBDDNR, LINENR, IMPULSENR }; /** Input color profile used to decoded image * NOINPUTCS: No input color profile. * EMBEDDED: Use the camera profile embedded in RAW file if exist. * CUSTOMINPUTCS: Use a custom input color space profile. */ enum InputColorSpace { NOINPUTCS = 0, EMBEDDED, CUSTOMINPUTCS }; /** Output RGB color space used to decoded image * RAWCOLOR: No output color profile (Linear RAW). * SRGB: Use standard sRGB color space. * ADOBERGB: Use standard Adobe RGB color space. * WIDEGAMMUT: Use standard RGB Wide Gamut color space. * PROPHOTO: Use standard RGB Pro Photo color space. * CUSTOMOUTPUTCS: Use a custom workspace color profile. */ enum OutputColorSpace { RAWCOLOR = 0, SRGB, ADOBERGB, WIDEGAMMUT, PROPHOTO, CUSTOMOUTPUTCS }; /** Standard constructor with default settings */ RawDecodingSettings(); /** Equivalent to the copy constructor */ RawDecodingSettings& operator=(const RawDecodingSettings& prm); /** Compare for equality */ bool operator==(const RawDecodingSettings& o) const; /** Standard destructor */ virtual ~RawDecodingSettings(); /** Method to use a settings to optimize time loading, for example to compute image histogram */ void optimizeTimeLoading(); /** Methods to read/write settings from/to a config file */ void readSettings(KConfigGroup& group); void writeSettings(KConfigGroup& group); public: /** If true, images with overblown channels are processed much more accurate, * without 'pink clouds' (and blue highlights under tungsteen lamps). */ bool fixColorsHighlights; /** If false, use a fixed white level, ignoring the image histogram. */ bool autoBrightness; /** Turn on RAW file decoding in 16 bits per color per pixel instead 8 bits. */ bool sixteenBitsImage; /** Half-size color image decoding (twice as fast as "enableRAWQuality"). * Turn on this option to reduce time loading to render histogram for example, * no to render an image to screen. */ bool halfSizeColorImage; /** White balance type to use. See WhiteBalance values for detail */ WhiteBalance whiteBalance; /** The temperature and the green multiplier of the custom white balance */ int customWhiteBalance; double customWhiteBalanceGreen; /** Turn on RAW file decoding using RGB interpolation as four colors. */ bool RGBInterpolate4Colors; /** For cameras with non-square pixels, do not stretch the image to its * correct aspect ratio. In any case, this option guarantees that each * output pixel corresponds to one RAW pixel. */ bool DontStretchPixels; /** Unclip Highlight color level: * 0 = Clip all highlights to solid white. * 1 = Leave highlights unclipped in various shades of pink. * 2 = Blend clipped and unclipped values together for a gradual * fade to white. * 3-9 = Reconstruct highlights. Low numbers favor whites; high numbers * favor colors. */ int unclipColors; /** RAW quality decoding factor value. See DecodingQuality values * for details. */ DecodingQuality RAWQuality; /** After interpolation, clean up color artifacts by repeatedly applying * a 3x3 median filter to the R-G and B-G channels. */ int medianFilterPasses; /** Noise reduction method to apply before demosaicing. */ NoiseReduction NRType; /** Noise reduction threshold value. Null value disable NR. Range is between 100 and 1000. * For IMPULSENR : set the amount of Luminance impulse denoise. */ int NRThreshold; /** Turn on chromatic aberrations correction * @deprecated does not work with libraw>=0.19 */ bool enableCACorrection; /** Magnification factor for Red and Blue layers * - caMultiplier[0] = amount of correction on red-green axis. * - caMultiplier[1] = amount of correction on blue-yellow axis. * - Both values set to 0.0 = automatic CA correction. * @deprecated does not work with libraw>=0.19 */ double caMultiplier[2]; /** Brightness of output image. */ double brightness; /** Turn on the black point setting to decode RAW image. */ bool enableBlackPoint; /** Black Point value of output image. */ int blackPoint; /** Turn on the white point setting to decode RAW image. */ bool enableWhitePoint; /** White Point value of output image. */ int whitePoint; /** The input color profile used to decoded RAW data. See OutputColorProfile * values for details. */ InputColorSpace inputColorSpace; /** Path to custom input ICC profile to define the camera's raw colorspace. */ QString inputProfile; /** The output color profile used to decoded RAW data. See OutputColorProfile * values for details. */ OutputColorSpace outputColorSpace; /** Path to custom output ICC profile to define the color workspace. */ QString outputProfile; /** Path to text file including dead pixel list. */ QString deadPixelMap; /** Rectangle used to calculate the white balance by averaging the region of image. */ QRect whiteBalanceArea; //-- Extended demosaicing settings ---------------------------------------------------------- /// For DCB interpolation. /** Number of DCB median filtering correction passes. * -1 : disable (default) * 1-10 : DCB correction passes */ int dcbIterations; /** Turn on the DCB interpolation with enhance interpolated colors. */ bool dcbEnhanceFl; /// For VCD_AHD interpolation. /** Turn on the EECI refine for VCD Demosaicing. * @deprecated does not work with libraw>=0.19 */ bool eeciRefine; /** Use edge-sensitive median filtering for artifact supression after VCD demosaicing. * 0 : disable (default) * 1-10 : median filter passes. * @deprecated does not work with libraw>=0.19 */ int esMedPasses; /** For IMPULSENR Noise reduction. Set the amount of Chrominance impulse denoise. * Null value disable NR. Range is between 100 and 1000. * @deprecated does not work with libraw>=0.19 */ int NRChroThreshold; /** Turn on the Exposure Correction before interpolation. */ bool expoCorrection; /** Shift of Exposure Correction before interpolation in linear scale. * Usable range is from 0.25 (darken image 1 stop : -2EV) to 8.0 (lighten ~1.5 photographic stops : +3EV). */ double expoCorrectionShift; /** Amount of highlight preservation for exposure correction before interpolation in E.V. * Usable range is from 0.0 (linear exposure shift, highlights may blow) to 1.0 (maximum highlights preservation) * This settings can only take effect if expoCorrectionShift > 1.0. */ double expoCorrectionHighlight; }; //! qDebug() stream operator. Writes settings @a s to the debug output in a nicely formatted way. QDebug operator<<(QDebug dbg, const RawDecodingSettings& s); } // namespace KDcrawIface #endif /* RAW_DECODING_SETTINGS_H */ diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rawfiles.h b/plugins/impex/raw/3rdparty/libkdcraw/src/rawfiles.h index 34783c43bf..c4b43e8e25 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/rawfiles.h +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rawfiles.h @@ -1,99 +1,99 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2005-11-06 * @brief list of RAW file extensions supported by libraw * * @author Copyright (C) 2005-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef RAW_FILES_H #define RAW_FILES_H -// NOTE: extension list Version 1 and 2 are taken from http://www.cybercom.net/~dcoffin/dcraw/rawphoto.c +// NOTE: extension list Version 1 and 2 are taken from https://www.dechifro.org/dcraw/rawphoto.c // Ext Descriptions From // www.file-extensions.org // en.wikipedia.org/wiki/RAW_file_format // filext.com static const char raw_file_extentions[] = // NOTE: VERSION 1 "*.bay " // Casio Digital Camera Raw File Format. "*.bmq " // NuCore Raw Image File. "*.cr2 " // Canon Digital Camera RAW Image Format version 2.0. These images are based on the TIFF image standard. "*.crw " // Canon Digital Camera RAW Image Format version 1.0. "*.cs1 " // Capture Shop Raw Image File. "*.dc2 " // Kodak DC25 Digital Camera File. "*.dcr " // Kodak Digital Camera Raw Image Format for these models: Kodak DSC Pro SLR/c, Kodak DSC Pro SLR/n, Kodak DSC Pro 14N, Kodak DSC PRO 14nx. "*.dng " // Adobe Digital Negative: DNG is publicly available archival format for the raw files generated by digital cameras. By addressing the lack of an open standard for the raw files created by individual camera models, DNG helps ensure that photographers will be able to access their files in the future. "*.erf " // Epson Digital Camera Raw Image Format. "*.fff " // Imacon Digital Camera Raw Image Format. "*.hdr " // Leaf Raw Image File. "*.k25 " // Kodak DC25 Digital Camera Raw Image Format. "*.kdc " // Kodak Digital Camera Raw Image Format. "*.mdc " // Minolta RD175 Digital Camera Raw Image Format. "*.mos " // Mamiya Digital Camera Raw Image Format. "*.mrw " // Minolta Dimage Digital Camera Raw Image Format. "*.nef " // Nikon Digital Camera Raw Image Format. "*.orf " // Olympus Digital Camera Raw Image Format. "*.pef " // Pentax Digital Camera Raw Image Format. "*.pxn " // Logitech Digital Camera Raw Image Format. "*.raf " // Fuji Digital Camera Raw Image Format. "*.raw " // Panasonic Digital Camera Image Format. "*.rdc " // Digital Foto Maker Raw Image File. "*.sr2 " // Sony Digital Camera Raw Image Format. "*.srf " // Sony Digital Camera Raw Image Format for DSC-F828 8 megapixel digital camera or Sony DSC-R1 "*.x3f " // Sigma Digital Camera Raw Image Format for devices based on Foveon X3 direct image sensor. "*.arw " // Sony Digital Camera Raw Image Format for Alpha devices. // NOTE: VERSION 2 "*.3fr " // Hasselblad Digital Camera Raw Image Format. "*.cine " // Phantom Software Raw Image File. "*.ia " // Sinar Raw Image File. "*.kc2 " // Kodak DCS200 Digital Camera Raw Image Format. "*.mef " // Mamiya Digital Camera Raw Image Format. "*.nrw " // Nikon Digital Camera Raw Image Format. "*.qtk " // Apple Quicktake 100/150 Digital Camera Raw Image Format. "*.rw2 " // Panasonic LX3 Digital Camera Raw Image Format. "*.sti " // Sinar Capture Shop Raw Image File. // NOTE: VERSION 3 "*.rwl " // Leica Digital Camera Raw Image Format. // NOTE: VERSION 4 "*.srw "; // Samnsung Raw Image Format. /* TODO: check if these format are supported "*.drf " // Kodak Digital Camera Raw Image Format. "*.dsc " // Kodak Digital Camera Raw Image Format. "*.ptx " // Pentax Digital Camera Raw Image Format. "*.cap " // Phase One Digital Camera Raw Image Format. "*.iiq " // Phase One Digital Camera Raw Image Format. "*.rwz " // Rawzor Digital Camera Raw Image Format. */ // increment this number whenever you change the above string static const int raw_file_extensions_version = 4; #endif // RAW_FILES_H diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rcombobox.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/rcombobox.cpp index c7e1d34d03..f41d89848f 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/rcombobox.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rcombobox.cpp @@ -1,156 +1,156 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2008-08-16 * @brief a combo box widget re-implemented with a * reset button to switch to a default item * * @author Copyright (C) 2008-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "rcombobox.h" // Qt includes #include #include #include #include #include // KDE includes #include #include namespace KDcrawIface { class Q_DECL_HIDDEN RComboBox::Private { public: Private() { defaultIndex = 0; resetButton = 0; combo = 0; } int defaultIndex; QToolButton* resetButton; QComboBox* combo; }; RComboBox::RComboBox(QWidget* const parent) : QWidget(parent), d(new Private) { QHBoxLayout* const hlay = new QHBoxLayout(this); d->combo = new QComboBox(this); d->resetButton = new QToolButton(this); d->resetButton->setAutoRaise(true); d->resetButton->setFocusPolicy(Qt::NoFocus); d->resetButton->setIcon(KisIconUtils::loadIcon("document-revert").pixmap(16, 16)); d->resetButton->setToolTip(i18nc("@info:tooltip", "Reset to default value")); hlay->addWidget(d->combo); hlay->addWidget(d->resetButton); hlay->setStretchFactor(d->combo, 10); hlay->setMargin(0); hlay->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); // ------------------------------------------------------------- connect(d->resetButton, &QToolButton::clicked, this, &RComboBox::slotReset); connect(d->combo, static_cast(&QComboBox::activated), this, &RComboBox::slotItemActivated); connect(d->combo, static_cast(&QComboBox::currentIndexChanged), this, &RComboBox::slotCurrentIndexChanged); } RComboBox::~RComboBox() { delete d; } QComboBox* RComboBox::combo() const { return d->combo; } void RComboBox::addItem(const QString& t, int index) { d->combo->addItem(t, index); } void RComboBox::insertItem(int index, const QString& t) { d->combo->insertItem(index, t); } int RComboBox::currentIndex() const { return d->combo->currentIndex(); } void RComboBox::setCurrentIndex(int v) { d->combo->setCurrentIndex(v); } int RComboBox::defaultIndex() const { return d->defaultIndex; } void RComboBox::setDefaultIndex(int v) { d->defaultIndex = v; d->combo->setCurrentIndex(d->defaultIndex); slotItemActivated(v); } void RComboBox::slotReset() { d->combo->setCurrentIndex(d->defaultIndex); d->resetButton->setEnabled(false); slotItemActivated(d->defaultIndex); emit reset(); } void RComboBox::slotItemActivated(int v) { d->resetButton->setEnabled(v != d->defaultIndex); emit activated(v); } void RComboBox::slotCurrentIndexChanged(int v) { d->resetButton->setEnabled(v != d->defaultIndex); emit currentIndexChanged(v); } } // namespace KDcrawIface diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rcombobox.h b/plugins/impex/raw/3rdparty/libkdcraw/src/rcombobox.h index 0dd29d2e3c..8b6c40c4b1 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/rcombobox.h +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rcombobox.h @@ -1,86 +1,86 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2008-08-16 * @brief a combo box widget re-implemented with a * reset button to switch to a default item * * @author Copyright (C) 2008-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef RCOMBOBOX_H #define RCOMBOBOX_H // Qt includes #include #include // Local includes namespace KDcrawIface { class RComboBox : public QWidget { Q_OBJECT public: RComboBox(QWidget* const parent=0); ~RComboBox() override; void setCurrentIndex(int d); int currentIndex() const; void setDefaultIndex(int d); int defaultIndex() const; QComboBox* combo() const; void addItem(const QString& t, int index = -1); void insertItem(int index, const QString& t); Q_SIGNALS: void reset(); void activated(int); void currentIndexChanged(int); public Q_SLOTS: void slotReset(); private Q_SLOTS: void slotItemActivated(int); void slotCurrentIndexChanged(int); private: class Private; Private* const d; }; } // namespace KDcrawIface #endif /* RCOMBOBOX_H */ diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rexpanderbox.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/rexpanderbox.cpp index 2d188a152d..2ed1176281 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/rexpanderbox.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rexpanderbox.cpp @@ -1,826 +1,826 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2008-03-14 * @brief A widget to host settings as expander box * * @author Copyright (C) 2008-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2008-2013 by Marcel Wiesweg * marcel dot wiesweg at gmx dot de * @author Copyright (C) 2010 by Manuel Viet * contact at 13zenrv dot fr * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "rexpanderbox.h" // Qt includes #include #include #include #include #include #include #include #include // KDE includes #include #include namespace KDcrawIface { RClickLabel::RClickLabel(QWidget* const parent) : QLabel(parent) { setCursor(Qt::PointingHandCursor); } RClickLabel::RClickLabel(const QString& text, QWidget* const parent) : QLabel(text, parent) { setCursor(Qt::PointingHandCursor); } RClickLabel::~RClickLabel() { } void RClickLabel::mousePressEvent(QMouseEvent* event) { QLabel::mousePressEvent(event); /* * In some contexts, like QGraphicsView, there will be no * release event if the press event was not accepted. */ if (event->button() == Qt::LeftButton) { event->accept(); } } void RClickLabel::mouseReleaseEvent(QMouseEvent* event) { QLabel::mouseReleaseEvent(event); if (event->button() == Qt::LeftButton) { emit leftClicked(); emit activated(); event->accept(); } } void RClickLabel::keyPressEvent(QKeyEvent* e) { switch (e->key()) { case Qt::Key_Down: case Qt::Key_Right: case Qt::Key_Space: emit activated(); return; default: break; } QLabel::keyPressEvent(e); } // ------------------------------------------------------------------------ RSqueezedClickLabel::RSqueezedClickLabel(QWidget* const parent) : RAdjustableLabel(parent) { setCursor(Qt::PointingHandCursor); } RSqueezedClickLabel::RSqueezedClickLabel(const QString& text, QWidget* const parent) : RAdjustableLabel(parent) { setAdjustedText(text); setCursor(Qt::PointingHandCursor); } RSqueezedClickLabel::~RSqueezedClickLabel() { } void RSqueezedClickLabel::mouseReleaseEvent(QMouseEvent* event) { QLabel::mouseReleaseEvent(event); if (event->button() == Qt::LeftButton) { emit leftClicked(); emit activated(); event->accept(); } } void RSqueezedClickLabel::mousePressEvent(QMouseEvent* event) { QLabel::mousePressEvent(event); /* * In some contexts, like QGraphicsView, there will be no * release event if the press event was not accepted. */ if (event->button() == Qt::LeftButton) { event->accept(); } } void RSqueezedClickLabel::keyPressEvent(QKeyEvent* e) { switch (e->key()) { case Qt::Key_Down: case Qt::Key_Right: case Qt::Key_Space: emit activated(); return; default: break; } QLabel::keyPressEvent(e); } // ------------------------------------------------------------------------ RArrowClickLabel::RArrowClickLabel(QWidget* const parent) : QWidget(parent), m_arrowType(Qt::DownArrow) { setCursor(Qt::PointingHandCursor); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_size = 8; m_margin = 2; } void RArrowClickLabel::setArrowType(Qt::ArrowType type) { m_arrowType = type; update(); } RArrowClickLabel::~RArrowClickLabel() { } Qt::ArrowType RArrowClickLabel::arrowType() const { return m_arrowType; } void RArrowClickLabel::mousePressEvent(QMouseEvent* event) { /* * In some contexts, like QGraphicsView, there will be no * release event if the press event was not accepted. */ if (event->button() == Qt::LeftButton) { event->accept(); } } void RArrowClickLabel::mouseReleaseEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton) { emit leftClicked(); } } void RArrowClickLabel::paintEvent(QPaintEvent*) { // Inspired by karrowbutton.cpp, // Copyright (C) 2001 Frerich Raabe QPainter p(this); QStyleOptionFrame opt; opt.init(this); opt.lineWidth = 2; opt.midLineWidth = 0; /* p.fillRect( rect(), palette().brush( QPalette::Background ) ); style()->drawPrimitive( QStyle::PE_Frame, &opt, &p, this); */ if (m_arrowType == Qt::NoArrow) return; if (width() < m_size + m_margin || height() < m_size + m_margin) return; // don't draw arrows if we are too small unsigned int x = 0, y = 0; if (m_arrowType == Qt::DownArrow) { x = (width() - m_size) / 2; y = height() - (m_size + m_margin); } else if (m_arrowType == Qt::UpArrow) { x = (width() - m_size) / 2; y = m_margin; } else if (m_arrowType == Qt::RightArrow) { x = width() - (m_size + m_margin); y = (height() - m_size) / 2; } else // arrowType == LeftArrow { x = m_margin; y = (height() - m_size) / 2; } /* if (isDown()) { ++x; ++y; } */ QStyle::PrimitiveElement e = QStyle::PE_IndicatorArrowLeft; switch (m_arrowType) { case Qt::LeftArrow: e = QStyle::PE_IndicatorArrowLeft; break; case Qt::RightArrow: e = QStyle::PE_IndicatorArrowRight; break; case Qt::UpArrow: e = QStyle::PE_IndicatorArrowUp; break; case Qt::DownArrow: e = QStyle::PE_IndicatorArrowDown; break; case Qt::NoArrow: break; } opt.state |= QStyle::State_Enabled; opt.rect = QRect( x, y, m_size, m_size); style()->drawPrimitive( e, &opt, &p, this ); } QSize RArrowClickLabel::sizeHint() const { return QSize(m_size + 2*m_margin, m_size + 2*m_margin); } // ------------------------------------------------------------------------ class Q_DECL_HIDDEN RLabelExpander::Private { public: Private() { clickLabel = 0; containerWidget = 0; pixmapLabel = 0; grid = 0; arrow = 0; line = 0; hbox = 0; checkBox = 0; expandByDefault = true; } bool expandByDefault; QCheckBox* checkBox; QLabel* pixmapLabel; QWidget* containerWidget; QGridLayout* grid; RLineWidget* line; QWidget* hbox; RArrowClickLabel* arrow; RClickLabel* clickLabel; }; RLabelExpander::RLabelExpander(QWidget* const parent) : QWidget(parent), d(new Private) { d->grid = new QGridLayout(this); d->line = new RLineWidget(Qt::Horizontal, this); d->hbox = new QWidget(this); d->arrow = new RArrowClickLabel(d->hbox); d->checkBox = new QCheckBox(d->hbox); d->pixmapLabel = new QLabel(d->hbox); d->clickLabel = new RClickLabel(d->hbox); QHBoxLayout* const hlay = new QHBoxLayout(d->hbox); hlay->addWidget(d->arrow); hlay->addWidget(d->checkBox); hlay->addWidget(d->pixmapLabel); hlay->addWidget(d->clickLabel, 10); hlay->setMargin(0); hlay->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); d->pixmapLabel->installEventFilter(this); d->pixmapLabel->setCursor(Qt::PointingHandCursor); d->hbox->setCursor(Qt::PointingHandCursor); setCheckBoxVisible(false); d->grid->addWidget(d->line, 0, 0, 1, 3); d->grid->addWidget(d->hbox, 1, 0, 1, 3); d->grid->setColumnStretch(2, 10); d->grid->setMargin(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); d->grid->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); connect(d->arrow, &RArrowClickLabel::leftClicked, this, &RLabelExpander::slotToggleContainer); connect(d->clickLabel, &RClickLabel::activated, this, &RLabelExpander::slotToggleContainer); connect(d->checkBox, &QCheckBox::toggled, this, &RLabelExpander::signalToggled); } RLabelExpander::~RLabelExpander() { delete d; } void RLabelExpander::setCheckBoxVisible(bool b) { d->checkBox->setVisible(b); } bool RLabelExpander::checkBoxIsVisible() const { return d->checkBox->isVisible(); } void RLabelExpander::setChecked(bool b) { d->checkBox->setChecked(b); } bool RLabelExpander::isChecked() const { return d->checkBox->isChecked(); } void RLabelExpander::setLineVisible(bool b) { d->line->setVisible(b); } bool RLabelExpander::lineIsVisible() const { return d->line->isVisible(); } void RLabelExpander::setText(const QString& txt) { d->clickLabel->setText(QString("%1").arg(txt)); } QString RLabelExpander::text() const { return d->clickLabel->text(); } void RLabelExpander::setIcon(const QIcon& icon) { d->pixmapLabel->setPixmap(icon.pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize))); } QIcon RLabelExpander::icon() const { return QIcon(*d->pixmapLabel->pixmap()); } void RLabelExpander::setWidget(QWidget* const widget) { if (widget) { d->containerWidget = widget; d->containerWidget->setParent(this); d->grid->addWidget(d->containerWidget, 2, 0, 1, 3); } } QWidget* RLabelExpander::widget() const { return d->containerWidget; } void RLabelExpander::setExpandByDefault(bool b) { d->expandByDefault = b; } bool RLabelExpander::isExpandByDefault() const { return d->expandByDefault; } void RLabelExpander::setExpanded(bool b) { if (d->containerWidget) { d->containerWidget->setVisible(b); if (b) d->arrow->setArrowType(Qt::DownArrow); else d->arrow->setArrowType(Qt::RightArrow); } emit signalExpanded(b); } bool RLabelExpander::isExpanded() const { return (d->arrow->arrowType() == Qt::DownArrow); } void RLabelExpander::slotToggleContainer() { if (d->containerWidget) setExpanded(!d->containerWidget->isVisible()); } bool RLabelExpander::eventFilter(QObject* obj, QEvent* ev) { if ( obj == d->pixmapLabel) { if ( ev->type() == QEvent::MouseButtonRelease) { slotToggleContainer(); return false; } else { return false; } } else { // pass the event on to the parent class return QWidget::eventFilter(obj, ev); } } // ------------------------------------------------------------------------ class Q_DECL_HIDDEN RExpanderBox::Private { public: Private(RExpanderBox* const box) { parent = box; vbox = 0; } void createItem(int index, QWidget* const w, const QIcon& icon, const QString& txt, const QString& objName, bool expandBydefault) { RLabelExpander* const exp = new RLabelExpander(parent->viewport()); exp->setText(txt); exp->setIcon(icon.pixmap(QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize))); exp->setWidget(w); exp->setLineVisible(!wList.isEmpty()); exp->setObjectName(objName); exp->setExpandByDefault(expandBydefault); if (index >= 0) { vbox->insertWidget(index, exp); wList.insert(index, exp); } else { vbox->addWidget(exp); wList.append(exp); } parent->connect(exp, SIGNAL(signalExpanded(bool)), parent, SLOT(slotItemExpanded(bool))); parent->connect(exp, SIGNAL(signalToggled(bool)), parent, SLOT(slotItemToggled(bool))); } public: QList wList; QVBoxLayout* vbox; RExpanderBox* parent; }; RExpanderBox::RExpanderBox(QWidget* const parent) : QScrollArea(parent), d(new Private(this)) { setFrameStyle(QFrame::NoFrame); setWidgetResizable(true); QWidget* const main = new QWidget(viewport()); d->vbox = new QVBoxLayout(main); d->vbox->setMargin(0); d->vbox->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); setWidget(main); setAutoFillBackground(false); viewport()->setAutoFillBackground(false); main->setAutoFillBackground(false); } RExpanderBox::~RExpanderBox() { d->wList.clear(); delete d; } void RExpanderBox::setCheckBoxVisible(int index, bool b) { if (index > d->wList.count() || index < 0) return; d->wList[index]->setCheckBoxVisible(b); } bool RExpanderBox::checkBoxIsVisible(int index) const { if (index > d->wList.count() || index < 0) return false; return d->wList[index]->checkBoxIsVisible(); } void RExpanderBox::setChecked(int index, bool b) { if (index > d->wList.count() || index < 0) return; d->wList[index]->setChecked(b); } bool RExpanderBox::isChecked(int index) const { if (index > d->wList.count() || index < 0) return false; return d->wList[index]->isChecked(); } void RExpanderBox::addItem(QWidget* const w, const QIcon& icon, const QString& txt, const QString& objName, bool expandBydefault) { d->createItem(-1, w, icon, txt, objName, expandBydefault); } void RExpanderBox::addItem(QWidget* const w, const QString& txt, const QString& objName, bool expandBydefault) { addItem(w, QIcon(), txt, objName, expandBydefault); } void RExpanderBox::addStretch() { d->vbox->addStretch(10); } void RExpanderBox::insertItem(int index, QWidget* const w, const QIcon& icon, const QString& txt, const QString& objName, bool expandBydefault) { d->createItem(index, w, icon, txt, objName, expandBydefault); } void RExpanderBox::slotItemExpanded(bool b) { RLabelExpander* const exp = dynamic_cast(sender()); if (exp) { int index = indexOf(exp); emit signalItemExpanded(index, b); } } void RExpanderBox::slotItemToggled(bool b) { RLabelExpander* const exp = dynamic_cast(sender()); if (exp) { int index = indexOf(exp); emit signalItemToggled(index, b); } } void RExpanderBox::insertItem(int index, QWidget* const w, const QString& txt, const QString& objName, bool expandBydefault) { insertItem(index, w, QIcon(), txt, objName, expandBydefault); } void RExpanderBox::insertStretch(int index) { d->vbox->insertStretch(index, 10); } void RExpanderBox::removeItem(int index) { if (index > d->wList.count() || index < 0) return; d->wList[index]->hide(); d->wList.removeAt(index); } void RExpanderBox::setItemText(int index, const QString& txt) { if (index > d->wList.count() || index < 0) return; d->wList[index]->setText(txt); } QString RExpanderBox::itemText(int index) const { if (index > d->wList.count() || index < 0) return QString(); return d->wList[index]->text(); } void RExpanderBox::setItemIcon(int index, const QIcon& icon) { if (index > d->wList.count() || index < 0) return; d->wList[index]->setIcon(icon.pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize))); } QIcon RExpanderBox::itemIcon(int index) const { if (index > d->wList.count() || index < 0) return QIcon(); return d->wList[index]->icon(); } int RExpanderBox::count() const { return d->wList.count(); } void RExpanderBox::setItemToolTip(int index, const QString& tip) { if (index > d->wList.count() || index < 0) return; d->wList[index]->setToolTip(tip); } QString RExpanderBox::itemToolTip(int index) const { if (index > d->wList.count() || index < 0) return QString(); return d->wList[index]->toolTip(); } void RExpanderBox::setItemEnabled(int index, bool enabled) { if (index > d->wList.count() || index < 0) return; d->wList[index]->setEnabled(enabled); } bool RExpanderBox::isItemEnabled(int index) const { if (index > d->wList.count() || index < 0) return false; return d->wList[index]->isEnabled(); } RLabelExpander* RExpanderBox::widget(int index) const { if (index > d->wList.count() || index < 0) return 0; return d->wList[index]; } int RExpanderBox::indexOf(RLabelExpander* const widget) const { for (int i = 0 ; i < count(); ++i) { RLabelExpander* const exp = d->wList[i]; if (widget == exp) return i; } return -1; } void RExpanderBox::setItemExpanded(int index, bool b) { if (index > d->wList.count() || index < 0) return; RLabelExpander* const exp = d->wList[index]; if (!exp) return; exp->setExpanded(b); } bool RExpanderBox::isItemExpanded(int index) const { if (index > d->wList.count() || index < 0) return false; RLabelExpander* const exp = d->wList[index]; if (!exp) return false; return (exp->isExpanded()); } void RExpanderBox::readSettings(KConfigGroup& group) { for (int i = 0 ; i < count(); ++i) { RLabelExpander* const exp = d->wList[i]; if (exp) { exp->setExpanded(group.readEntry(QString("%1 Expanded").arg(exp->objectName()), exp->isExpandByDefault())); } } } void RExpanderBox::writeSettings(KConfigGroup& group) { for (int i = 0 ; i < count(); ++i) { RLabelExpander* const exp = d->wList[i]; if (exp) { group.writeEntry(QString("%1 Expanded").arg(exp->objectName()), exp->isExpanded()); } } } // ------------------------------------------------------------------------ RExpanderBoxExclusive::RExpanderBoxExclusive(QWidget* const parent) : RExpanderBox(parent) { setIsToolBox(true); } RExpanderBoxExclusive::~RExpanderBoxExclusive() { } void RExpanderBoxExclusive::slotItemExpanded(bool b) { RLabelExpander* const exp = dynamic_cast(sender()); if (!exp) return; if (isToolBox() && b) { int item = 0; while (item < count()) { if (isItemExpanded(item) && item != indexOf(exp)) { setItemExpanded(item, false); } item++; } } emit signalItemExpanded(indexOf(exp), b); } void RExpanderBoxExclusive::setIsToolBox(bool b) { m_toolbox = b; } bool RExpanderBoxExclusive::isToolBox() const { return (m_toolbox); } } // namespace KDcrawIface diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rexpanderbox.h b/plugins/impex/raw/3rdparty/libkdcraw/src/rexpanderbox.h index 8cd0675b57..13d36b5d00 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/rexpanderbox.h +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rexpanderbox.h @@ -1,299 +1,299 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2008-03-14 * @brief A widget to host settings as expander box * * @author Copyright (C) 2008-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2008-2013 by Marcel Wiesweg * marcel dot wiesweg at gmx dot de * @author Copyright (C) 2010 by Manuel Viet * contact at 13zenrv dot fr * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef REXPANDERBOX_H #define REXPANDERBOX_H // Qt includes #include #include #include #include #include // KDE includes #include // Local includes #include "rwidgetutils.h" namespace KDcrawIface { class RClickLabel : public QLabel { Q_OBJECT public: RClickLabel(QWidget* const parent = 0); explicit RClickLabel(const QString& text, QWidget* const parent = 0); ~RClickLabel() override; Q_SIGNALS: /// Emitted when activated by left mouse click void leftClicked(); /// Emitted when activated, by mouse or key press void activated(); protected: void mousePressEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; void keyPressEvent(QKeyEvent* event) override; }; // ------------------------------------------------------------------------- class RSqueezedClickLabel : public RAdjustableLabel { Q_OBJECT public: RSqueezedClickLabel(QWidget* const parent = 0); explicit RSqueezedClickLabel(const QString& text, QWidget* const parent = 0); ~RSqueezedClickLabel() override; Q_SIGNALS: void leftClicked(); void activated(); protected: void mousePressEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; void keyPressEvent(QKeyEvent* event) override; }; // ------------------------------------------------------------------------- class RArrowClickLabel : public QWidget { Q_OBJECT public: RArrowClickLabel(QWidget* const parent = 0); ~RArrowClickLabel() override; void setArrowType(Qt::ArrowType arrowType); Qt::ArrowType arrowType() const; QSize sizeHint () const override; Q_SIGNALS: void leftClicked(); protected: void mousePressEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; void paintEvent(QPaintEvent* event) override; protected: Qt::ArrowType m_arrowType; int m_size; int m_margin; }; // ------------------------------------------------------------------------- class RLabelExpander : public QWidget { Q_OBJECT public: RLabelExpander(QWidget* const parent = 0); ~RLabelExpander() override; void setCheckBoxVisible(bool b); bool checkBoxIsVisible() const; void setChecked(bool b); bool isChecked() const; void setLineVisible(bool b); bool lineIsVisible() const; void setText(const QString& txt); QString text() const; void setIcon(const QIcon &icon); QIcon icon() const; void setWidget(QWidget* const widget); QWidget* widget() const; void setExpanded(bool b); bool isExpanded() const; void setExpandByDefault(bool b); bool isExpandByDefault() const; Q_SIGNALS: void signalExpanded(bool); void signalToggled(bool); private Q_SLOTS: void slotToggleContainer(); private: bool eventFilter(QObject* obj, QEvent* ev) override; private: class Private; Private* const d; }; // ------------------------------------------------------------------------- class RExpanderBox : public QScrollArea { Q_OBJECT public: RExpanderBox(QWidget* const parent = 0); ~RExpanderBox() override; /** Add RLabelExpander item at end of box layout with these settings : 'w' : the widget hosted by RLabelExpander. 'pix' : pixmap used as icon to item title. 'txt' : text used as item title. 'objName' : item object name used to read/save expanded settings to rc file. 'expandBydefault' : item state by default (expanded or not). */ void addItem(QWidget* const w, const QIcon &icon, const QString& txt, const QString& objName, bool expandBydefault); void addItem(QWidget* const w, const QString& txt, const QString& objName, bool expandBydefault); /** Insert RLabelExpander item at box layout index with these settings : 'w' : the widget hosted by RLabelExpander. 'pix' : pixmap used as icon to item title. 'txt' : text used as item title. 'objName' : item object name used to read/save expanded settings to rc file. 'expandBydefault' : item state by default (expanded or not). */ void insertItem(int index, QWidget* const w, const QIcon &icon, const QString& txt, const QString& objName, bool expandBydefault); void insertItem(int index, QWidget* const w, const QString& txt, const QString& objName, bool expandBydefault); void removeItem(int index); void setCheckBoxVisible(int index, bool b); bool checkBoxIsVisible(int index) const; void setChecked(int index, bool b); bool isChecked(int index) const; void setItemText(int index, const QString& txt); QString itemText (int index) const; void setItemIcon(int index, const QIcon &icon); QIcon itemIcon(int index) const; void setItemToolTip(int index, const QString& tip); QString itemToolTip(int index) const; void setItemEnabled(int index, bool enabled); bool isItemEnabled(int index) const; void addStretch(); void insertStretch(int index); void setItemExpanded(int index, bool b); bool isItemExpanded(int index) const; int count() const; RLabelExpander* widget(int index) const; int indexOf(RLabelExpander* const widget) const; virtual void readSettings(KConfigGroup& group); virtual void writeSettings(KConfigGroup& group); Q_SIGNALS: void signalItemExpanded(int index, bool b); void signalItemToggled(int index, bool b); private Q_SLOTS: void slotItemExpanded(bool b); void slotItemToggled(bool b); private: class Private; Private* const d; }; // ------------------------------------------------------------------------- class RExpanderBoxExclusive : public RExpanderBox { Q_OBJECT public: RExpanderBoxExclusive(QWidget* const parent = 0); ~RExpanderBoxExclusive() override; /** Show one expander open at most */ void setIsToolBox(bool b); bool isToolBox() const; private Q_SLOTS: void slotItemExpanded(bool b); private: bool m_toolbox; }; } // namespace KDcrawIface #endif // REXPANDERBOX_H diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rnuminput.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/rnuminput.cpp index adcb5f66ea..63425d2fb0 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/rnuminput.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rnuminput.cpp @@ -1,257 +1,257 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2008-08-16 * @brief Integer and double num input widget * re-implemented with a reset button to switch to * a default value * * @author Copyright (C) 2008-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "rnuminput.h" // C++ includes #include // Qt includes #include #include #include #include #include // KDE includes #include // Local includes #include "rsliderspinbox.h" #include namespace KDcrawIface { class Q_DECL_HIDDEN RIntNumInput::Private { public: Private() { defaultValue = 0; resetButton = 0; input = 0; } int defaultValue; QToolButton* resetButton; RSliderSpinBox* input; }; RIntNumInput::RIntNumInput(QWidget* const parent) : QWidget(parent), d(new Private) { QHBoxLayout* const hlay = new QHBoxLayout(this); d->input = new RSliderSpinBox(this); d->resetButton = new QToolButton(this); d->resetButton->setAutoRaise(true); d->resetButton->setFocusPolicy(Qt::NoFocus); d->resetButton->setIcon(KisIconUtils::loadIcon("document-revert").pixmap(16, 16)); d->resetButton->setToolTip(i18nc("@info:tooltip", "Reset to default value")); hlay->addWidget(d->input); hlay->addWidget(d->resetButton); hlay->setContentsMargins(QMargins()); hlay->setStretchFactor(d->input, 10); hlay->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); // ------------------------------------------------------------- connect(d->resetButton, &QToolButton::clicked, this, &RIntNumInput::slotReset); connect(d->input, &RSliderSpinBox::valueChanged, this, &RIntNumInput::slotValueChanged); } RIntNumInput::~RIntNumInput() { delete d; } void RIntNumInput::setRange(int min, int max, int step) { d->input->setRange(min, max); d->input->setSingleStep(step); } int RIntNumInput::value() const { return d->input->value(); } void RIntNumInput::setValue(int v) { d->input->setValue(v); } int RIntNumInput::defaultValue() const { return d->defaultValue; } void RIntNumInput::setDefaultValue(int v) { d->defaultValue = v; d->input->setValue(d->defaultValue); slotValueChanged(v); } void RIntNumInput::setSuffix(const QString& suffix) { d->input->setSuffix(suffix); } void RIntNumInput::slotReset() { d->input->setValue(d->defaultValue); d->resetButton->setEnabled(false); emit reset(); } void RIntNumInput::slotValueChanged(int v) { d->resetButton->setEnabled(v != d->defaultValue); emit valueChanged(v); } // ---------------------------------------------------- class Q_DECL_HIDDEN RDoubleNumInput::Private { public: Private() { defaultValue = 0.0; resetButton = 0; input = 0; } double defaultValue; QToolButton* resetButton; RDoubleSliderSpinBox* input; }; RDoubleNumInput::RDoubleNumInput(QWidget* const parent) : QWidget(parent), d(new Private) { QHBoxLayout* const hlay = new QHBoxLayout(this); d->input = new RDoubleSliderSpinBox(this); d->resetButton = new QToolButton(this); d->resetButton->setAutoRaise(true); d->resetButton->setFocusPolicy(Qt::NoFocus); d->resetButton->setIcon(KisIconUtils::loadIcon("document-revert").pixmap(16, 16)); d->resetButton->setToolTip(i18nc("@info:tooltip", "Reset to default value")); hlay->addWidget(d->input); hlay->addWidget(d->resetButton); hlay->setContentsMargins(QMargins()); hlay->setStretchFactor(d->input, 10); hlay->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); // ------------------------------------------------------------- connect(d->resetButton, &QToolButton::clicked, this, &RDoubleNumInput::slotReset); connect(d->input, &RDoubleSliderSpinBox::valueChanged, this, &RDoubleNumInput::slotValueChanged); } RDoubleNumInput::~RDoubleNumInput() { delete d; } void RDoubleNumInput::setDecimals(int p) { d->input->setRange(d->input->minimum(), d->input->maximum(), p); } void RDoubleNumInput::setRange(double min, double max, double step) { d->input->setRange(min, max, (int) -floor(log10(step))); d->input->setFastSliderStep(5 * step); d->input->setSingleStep(step); } double RDoubleNumInput::value() const { return d->input->value(); } void RDoubleNumInput::setValue(double v) { d->input->setValue(v); } double RDoubleNumInput::defaultValue() const { return d->defaultValue; } void RDoubleNumInput::setDefaultValue(double v) { d->defaultValue = v; d->input->setValue(d->defaultValue); slotValueChanged(v); } void RDoubleNumInput::setSuffix(const QString& suffix) { d->input->setSuffix(suffix); } void RDoubleNumInput::slotReset() { d->input->setValue(d->defaultValue); d->resetButton->setEnabled(false); emit reset(); } void RDoubleNumInput::slotValueChanged(double v) { d->resetButton->setEnabled(v != d->defaultValue); emit valueChanged(v); } } // namespace KDcrawIface diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rnuminput.h b/plugins/impex/raw/3rdparty/libkdcraw/src/rnuminput.h index cb18e58339..2c8ff38607 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/rnuminput.h +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rnuminput.h @@ -1,121 +1,121 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2008-08-16 * @brief Integer and double num input widget * re-implemented with a reset button to switch to * a default value * * @author Copyright (C) 2008-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef RNUMINPUT_H #define RNUMINPUT_H // Qt includes #include // Local includes namespace KDcrawIface { class RIntNumInput : public QWidget { Q_OBJECT public: RIntNumInput(QWidget* const parent=0); ~RIntNumInput() override; void setRange(int min, int max, int step); void setDefaultValue(int d); int defaultValue() const; int value() const; void setSuffix(const QString& suffix); Q_SIGNALS: void reset(); void valueChanged(int); public Q_SLOTS: void setValue(int d); void slotReset(); private Q_SLOTS: void slotValueChanged(int); private: class Private; Private* const d; }; // --------------------------------------------------------- class RDoubleNumInput : public QWidget { Q_OBJECT public: RDoubleNumInput(QWidget* const parent=0); ~RDoubleNumInput() override; void setDecimals(int p); void setRange(double min, double max, double step); void setDefaultValue(double d); double defaultValue() const; double value() const; void setSuffix(const QString& suffix); Q_SIGNALS: void reset(); void valueChanged(double); public Q_SLOTS: void setValue(double d); void slotReset(); private Q_SLOTS: void slotValueChanged(double); private: class Private; Private* const d; }; } // namespace KDcrawIface #endif /* RNUMINPUT_H */ diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rsliderspinbox.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/rsliderspinbox.cpp index 0efcf9dbcb..b89263c04d 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/rsliderspinbox.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rsliderspinbox.cpp @@ -1,773 +1,772 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2014-11-30 * @brief Save space slider widget * * @author Copyright (C) 2014 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2010 by Justin Noel * justin at ics dot com * @author Copyright (C) 2010 by Cyrille Berger * cberger at cberger dot net * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "rsliderspinbox.h" // C++ includes #include // Qt includes #include #include #include #include #include #include #include #include #include #include namespace KDcrawIface { class RAbstractSliderSpinBoxPrivate { public: RAbstractSliderSpinBoxPrivate() { edit = 0; validator = 0; dummySpinBox = 0; upButtonDown = false; downButtonDown = false; shiftMode = false; factor = 1.0; fastSliderStep = 5; slowFactor = 0.1; shiftPercent = 0.0; exponentRatio = 0.0; value = 0; maximum = 100; minimum = 0; singleStep = 1; } QLineEdit* edit; QDoubleValidator* validator; bool upButtonDown; bool downButtonDown; int factor; int fastSliderStep; double slowFactor; double shiftPercent; bool shiftMode; QString suffix; double exponentRatio; int value; int maximum; int minimum; int singleStep; QSpinBox* dummySpinBox; }; RAbstractSliderSpinBox::RAbstractSliderSpinBox(QWidget* const parent, RAbstractSliderSpinBoxPrivate* const q) : QWidget(parent), d_ptr(q) { Q_D(RAbstractSliderSpinBox); d->edit = new QLineEdit(this); d->edit->setFrame(false); d->edit->setAlignment(Qt::AlignCenter); d->edit->hide(); d->edit->installEventFilter(this); // Make edit transparent d->edit->setAutoFillBackground(false); QPalette pal = d->edit->palette(); pal.setColor(QPalette::Base, Qt::transparent); d->edit->setPalette(pal); connect(d->edit, SIGNAL(editingFinished()), this, SLOT(editLostFocus())); d->validator = new QDoubleValidator(d->edit); d->edit->setValidator(d->validator); setExponentRatio(1.0); // Set sane defaults setFocusPolicy(Qt::StrongFocus); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); // dummy needed to fix a bug in the polyester theme d->dummySpinBox = new QSpinBox(this); d->dummySpinBox->hide(); } RAbstractSliderSpinBox::~RAbstractSliderSpinBox() { Q_D(RAbstractSliderSpinBox); delete d; } void RAbstractSliderSpinBox::showEdit() { Q_D(RAbstractSliderSpinBox); if (d->edit->isVisible()) return; d->edit->setGeometry(editRect(spinBoxOptions())); d->edit->setText(valueString()); d->edit->selectAll(); d->edit->show(); d->edit->setFocus(Qt::OtherFocusReason); update(); } void RAbstractSliderSpinBox::hideEdit() { Q_D(RAbstractSliderSpinBox); d->edit->hide(); update(); } void RAbstractSliderSpinBox::paintEvent(QPaintEvent* e) { Q_D(RAbstractSliderSpinBox); Q_UNUSED(e) QPainter painter(this); // Create options to draw spin box parts QStyleOptionSpinBox spinOpts = spinBoxOptions(); // Draw "SpinBox".Clip off the area of the lineEdit to avoid double borders being drawn painter.save(); painter.setClipping(true); QRect eraseRect(QPoint(rect().x(), rect().y()), QPoint(editRect(spinOpts).right(), rect().bottom())); painter.setClipRegion(QRegion(rect()).subtracted(eraseRect)); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); painter.setClipping(false); painter.restore(); // Create options to draw progress bar parts QStyleOptionProgressBar progressOpts = progressBarOptions(); // Draw "ProgressBar" in SpinBox style()->drawControl(QStyle::CE_ProgressBar, &progressOpts, &painter, 0); // Draw focus if necessary if (hasFocus() && d->edit->hasFocus()) { QStyleOptionFocusRect focusOpts; focusOpts.initFrom(this); focusOpts.rect = progressOpts.rect; focusOpts.backgroundColor = palette().color(QPalette::Window); style()->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpts, &painter, this); } } void RAbstractSliderSpinBox::mousePressEvent(QMouseEvent* e) { Q_D(RAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); // Depress buttons or highlight slider. Also used to emulate mouse grab. if (e->buttons() & Qt::LeftButton) { if (upButtonRect(spinOpts).contains(e->pos())) { d->upButtonDown = true; } else if (downButtonRect(spinOpts).contains(e->pos())) { d->downButtonDown = true; } } else if (e->buttons() & Qt::RightButton) { showEdit(); } update(); } void RAbstractSliderSpinBox::mouseReleaseEvent(QMouseEvent* e) { Q_D(RAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); // Step up/down for buttons. Emulating mouse grab too. if (upButtonRect(spinOpts).contains(e->pos()) && d->upButtonDown) { setInternalValue(d->value + d->singleStep); } else if (downButtonRect(spinOpts).contains(e->pos()) && d->downButtonDown) { setInternalValue(d->value - d->singleStep); } else if (editRect(spinOpts).contains(e->pos()) && !(d->edit->isVisible()) && !(d->upButtonDown || d->downButtonDown)) { // Snap to percentage for progress area setInternalValue(valueForX(e->pos().x(),e->modifiers())); } d->upButtonDown = false; d->downButtonDown = false; update(); } void RAbstractSliderSpinBox::mouseMoveEvent(QMouseEvent* e) { Q_D(RAbstractSliderSpinBox); if( e->modifiers() & Qt::ShiftModifier ) { if( !d->shiftMode ) { d->shiftPercent = pow(double(d->value - d->minimum)/double(d->maximum - d->minimum), 1/double(d->exponentRatio)); d->shiftMode = true; } } else { d->shiftMode = false; } // Respect emulated mouse grab. if (e->buttons() & Qt::LeftButton && !(d->downButtonDown || d->upButtonDown)) { setInternalValue(valueForX(e->pos().x(),e->modifiers())); update(); } } void RAbstractSliderSpinBox::keyPressEvent(QKeyEvent* e) { Q_D(RAbstractSliderSpinBox); switch (e->key()) { case Qt::Key_Up: case Qt::Key_Right: setInternalValue(d->value + d->singleStep); break; case Qt::Key_Down: case Qt::Key_Left: setInternalValue(d->value - d->singleStep); break; case Qt::Key_Shift: d->shiftPercent = pow( double(d->value - d->minimum)/double(d->maximum - d->minimum), 1/double(d->exponentRatio) ); d->shiftMode = true; break; case Qt::Key_Enter: // Line edit isn't "accepting" key strokes... case Qt::Key_Return: case Qt::Key_Escape: case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_AltGr: case Qt::Key_Super_L: case Qt::Key_Super_R: break; default: showEdit(); d->edit->event(e); break; } } void RAbstractSliderSpinBox::wheelEvent(QWheelEvent *e) { Q_D(RAbstractSliderSpinBox); int step = d->fastSliderStep; if( e->modifiers() & Qt::ShiftModifier ) { step = d->singleStep; } if ( e->delta() > 0) { setInternalValue(d->value + step); } else { setInternalValue(d->value - step); } update(); e->accept(); } bool RAbstractSliderSpinBox::eventFilter(QObject* recv, QEvent* e) { Q_D(RAbstractSliderSpinBox); if (recv == static_cast(d->edit) && e->type() == QEvent::KeyRelease) { QKeyEvent* const keyEvent = static_cast(e); switch (keyEvent->key()) { case Qt::Key_Enter: case Qt::Key_Return: setInternalValue(QLocale::system().toDouble(d->edit->text()) * d->factor); hideEdit(); return true; case Qt::Key_Escape: hideEdit(); return true; default: break; } } return false; } QSize RAbstractSliderSpinBox::sizeHint() const { const Q_D(RAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QFontMetrics fm(font()); // We need at least 50 pixels or things start to look bad int w = qMax(fm.width(QString::number(d->maximum)), 50); QSize hint(w, d->edit->sizeHint().height() + 3); // Getting the size of the buttons is a pain as the calcs require a rect // that is "big enough". We run the calc twice to get the "smallest" buttons // This code was inspired by QAbstractSpinBox. QSize extra(35, 6); spinOpts.rect.setSize(hint + extra); extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts, QStyle::SC_SpinBoxEditField, this).size(); spinOpts.rect.setSize(hint + extra); extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts, QStyle::SC_SpinBoxEditField, this).size(); hint += extra; spinOpts.rect = rect(); - return style()->sizeFromContents(QStyle::CT_SpinBox, &spinOpts, hint, 0) - .expandedTo(QApplication::globalStrut()); + return style()->sizeFromContents(QStyle::CT_SpinBox, &spinOpts, hint, 0); } QSize RAbstractSliderSpinBox::minimumSizeHint() const { return sizeHint(); } QStyleOptionSpinBox RAbstractSliderSpinBox::spinBoxOptions() const { const Q_D(RAbstractSliderSpinBox); QStyleOptionSpinBox opts; opts.initFrom(this); opts.frame = false; opts.buttonSymbols = QAbstractSpinBox::UpDownArrows; opts.subControls = QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown; // Disable non-logical buttons if (d->value == d->minimum) { opts.stepEnabled = QAbstractSpinBox::StepUpEnabled; } else if (d->value == d->maximum) { opts.stepEnabled = QAbstractSpinBox::StepDownEnabled; } else { opts.stepEnabled = QAbstractSpinBox::StepUpEnabled | QAbstractSpinBox::StepDownEnabled; } // Deal with depressed buttons if (d->upButtonDown) { opts.activeSubControls = QStyle::SC_SpinBoxUp; } else if (d->downButtonDown) { opts.activeSubControls = QStyle::SC_SpinBoxDown; } else { opts.activeSubControls = 0; } return opts; } QStyleOptionProgressBar RAbstractSliderSpinBox::progressBarOptions() const { const Q_D(RAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); // Create opts for drawing the progress portion QStyleOptionProgressBar progressOpts; progressOpts.initFrom(this); progressOpts.maximum = d->maximum; progressOpts.minimum = d->minimum; double minDbl = d->minimum; double dValues = (d->maximum - minDbl); progressOpts.progress = dValues * pow((d->value - minDbl) / dValues, 1.0 / d->exponentRatio) + minDbl; progressOpts.text = valueString() + d->suffix; progressOpts.textAlignment = Qt::AlignCenter; progressOpts.textVisible = !(d->edit->isVisible()); // Change opts rect to be only the ComboBox's text area progressOpts.rect = editRect(spinOpts); return progressOpts; } QRect RAbstractSliderSpinBox::editRect(const QStyleOptionSpinBox& spinBoxOptions) const { return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxEditField); } QRect RAbstractSliderSpinBox::progressRect(const QStyleOptionProgressBar& progressBarOptions) const { return style()->subElementRect(QStyle::SE_ProgressBarGroove, &progressBarOptions); } QRect RAbstractSliderSpinBox::upButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const { return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxUp); } QRect RAbstractSliderSpinBox::downButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const { return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxDown); } int RAbstractSliderSpinBox::valueForX(int x, Qt::KeyboardModifiers modifiers) const { const Q_D(RAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QStyleOptionProgressBar progressOpts = progressBarOptions(); // Adjust for magic number in style code (margins) QRect correctedProgRect = progressRect(progressOpts).adjusted(2, 2, -2, -2); // Compute the distance of the progress bar, in pixel double leftDbl = correctedProgRect.left(); double xDbl = x - leftDbl; // Compute the ration of the progress bar used, linearly (ignoring the exponent) double rightDbl = correctedProgRect.right(); double minDbl = d->minimum; double maxDbl = d->maximum; double dValues = (maxDbl - minDbl); double percent = (xDbl / (rightDbl - leftDbl)); // If SHIFT is pressed, movement should be slowed. if ( modifiers & Qt::ShiftModifier ) { percent = d->shiftPercent + (percent - d->shiftPercent) * d->slowFactor; } // Final value double realvalue = ((dValues * pow(percent, d->exponentRatio)) + minDbl); // If key CTRL is pressed, round to the closest step. if ( modifiers & Qt::ControlModifier ) { double fstep = d->fastSliderStep; if( modifiers & Qt::ShiftModifier ) { fstep *= d->slowFactor; } realvalue = floor((realvalue + fstep / 2) / fstep) * fstep; } // Return the value return int(realvalue); } void RAbstractSliderSpinBox::setSuffix(const QString& suffix) { Q_D(RAbstractSliderSpinBox); d->suffix = suffix; } void RAbstractSliderSpinBox::setExponentRatio(double dbl) { Q_D(RAbstractSliderSpinBox); Q_ASSERT(dbl > 0); d->exponentRatio = dbl; } void RAbstractSliderSpinBox::contextMenuEvent(QContextMenuEvent* event) { event->accept(); } void RAbstractSliderSpinBox::editLostFocus() { // only hide on focus lost, if editing is finished that will be handled in eventFilter Q_D(RAbstractSliderSpinBox); if (!d->edit->hasFocus()) { hideEdit(); } } // --------------------------------------------------------------------------------------------- class RSliderSpinBoxPrivate : public RAbstractSliderSpinBoxPrivate { }; RSliderSpinBox::RSliderSpinBox(QWidget* const parent) : RAbstractSliderSpinBox(parent, new RSliderSpinBoxPrivate) { setRange(0,99); } RSliderSpinBox::~RSliderSpinBox() { } void RSliderSpinBox::setRange(int minimum, int maximum) { Q_D(RSliderSpinBox); d->minimum = minimum; d->maximum = maximum; d->fastSliderStep = (maximum-minimum+1)/20; d->validator->setRange(minimum, maximum, 0); update(); } int RSliderSpinBox::minimum() const { const Q_D(RSliderSpinBox); return d->minimum; } void RSliderSpinBox::setMinimum(int minimum) { Q_D(RSliderSpinBox); setRange(minimum, d->maximum); } int RSliderSpinBox::maximum() const { const Q_D(RSliderSpinBox); return d->maximum; } void RSliderSpinBox::setMaximum(int maximum) { Q_D(RSliderSpinBox); setRange(d->minimum, maximum); } int RSliderSpinBox::fastSliderStep() const { const Q_D(RSliderSpinBox); return d->fastSliderStep; } void RSliderSpinBox::setFastSliderStep(int step) { Q_D(RSliderSpinBox); d->fastSliderStep = step; } int RSliderSpinBox::value() const { const Q_D(RSliderSpinBox); return d->value; } void RSliderSpinBox::setValue(int value) { setInternalValue(value); update(); } QString RSliderSpinBox::valueString() const { const Q_D(RSliderSpinBox); return QLocale::system().toString(d->value); } void RSliderSpinBox::setSingleStep(int value) { Q_D(RSliderSpinBox); d->singleStep = value; } void RSliderSpinBox::setPageStep(int value) { Q_UNUSED(value); } void RSliderSpinBox::setInternalValue(int _value) { Q_D(RAbstractSliderSpinBox); d->value = qBound(d->minimum, _value, d->maximum); emit(valueChanged(value())); } // --------------------------------------------------------------------------------------------- class RDoubleSliderSpinBoxPrivate : public RAbstractSliderSpinBoxPrivate { }; RDoubleSliderSpinBox::RDoubleSliderSpinBox(QWidget* const parent) : RAbstractSliderSpinBox(parent, new RDoubleSliderSpinBoxPrivate) { } RDoubleSliderSpinBox::~RDoubleSliderSpinBox() { } void RDoubleSliderSpinBox::setRange(double minimum, double maximum, int decimals) { Q_D(RDoubleSliderSpinBox); d->factor = pow(10.0, decimals); d->minimum = minimum * d->factor; d->maximum = maximum * d->factor; // This code auto-compute a new step when pressing control. // A flag defaulting to "do not change the fast step" should be added, but it implies changing every call if (maximum - minimum >= 2.0 || decimals <= 0) { //Quick step on integers d->fastSliderStep = int(pow(10.0, decimals)); } else if(decimals == 1) { d->fastSliderStep = (maximum-minimum)*d->factor/10; } else { d->fastSliderStep = (maximum-minimum)*d->factor/20; } d->validator->setRange(minimum, maximum, decimals); update(); setValue(value()); } double RDoubleSliderSpinBox::minimum() const { const Q_D(RAbstractSliderSpinBox); return d->minimum / d->factor; } void RDoubleSliderSpinBox::setMinimum(double minimum) { Q_D(RAbstractSliderSpinBox); setRange(minimum, d->maximum); } double RDoubleSliderSpinBox::maximum() const { const Q_D(RAbstractSliderSpinBox); return d->maximum / d->factor; } void RDoubleSliderSpinBox::setMaximum(double maximum) { Q_D(RAbstractSliderSpinBox); setRange(d->minimum, maximum); } double RDoubleSliderSpinBox::fastSliderStep() const { const Q_D(RAbstractSliderSpinBox); return d->fastSliderStep; } void RDoubleSliderSpinBox::setFastSliderStep(double step) { Q_D(RAbstractSliderSpinBox); d->fastSliderStep = step * d->factor; } double RDoubleSliderSpinBox::value() const { const Q_D(RAbstractSliderSpinBox); return (double)d->value / d->factor; } void RDoubleSliderSpinBox::setValue(double value) { Q_D(RAbstractSliderSpinBox); setInternalValue(d->value = qRound(value * d->factor)); update(); } void RDoubleSliderSpinBox::setSingleStep(double value) { Q_D(RAbstractSliderSpinBox); d->singleStep = value * d->factor; } QString RDoubleSliderSpinBox::valueString() const { const Q_D(RAbstractSliderSpinBox); return QLocale::system().toString((double)d->value / d->factor, 'f', d->validator->decimals()); } void RDoubleSliderSpinBox::setInternalValue(int val) { Q_D(RAbstractSliderSpinBox); d->value = qBound(d->minimum, val, d->maximum); emit(valueChanged(value())); } } // namespace KDcrawIface diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rsliderspinbox.h b/plugins/impex/raw/3rdparty/libkdcraw/src/rsliderspinbox.h index d1a1377647..0e79f3fce9 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/rsliderspinbox.h +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rsliderspinbox.h @@ -1,183 +1,183 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2014-11-30 * @brief Save space slider widget * * @author Copyright (C) 2014 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2010 by Justin Noel * justin at ics dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef RSLIDERSPINBOX_H #define RSLIDERSPINBOX_H // Qt includes #include #include #include #include #include namespace KDcrawIface { class RAbstractSliderSpinBoxPrivate; class RSliderSpinBoxPrivate; class RDoubleSliderSpinBoxPrivate; /** * TODO: when inactive, also show the progress bar part as inactive! */ class RAbstractSliderSpinBox : public QWidget { Q_OBJECT Q_DISABLE_COPY(RAbstractSliderSpinBox) Q_DECLARE_PRIVATE(RAbstractSliderSpinBox) protected: explicit RAbstractSliderSpinBox(QWidget* const parent, RAbstractSliderSpinBoxPrivate* const q); public: ~RAbstractSliderSpinBox() override; void showEdit(); void hideEdit(); void setSuffix(const QString& suffix); void setExponentRatio(double dbl); protected: void paintEvent(QPaintEvent* e) override; void mousePressEvent(QMouseEvent* e) override; void mouseReleaseEvent(QMouseEvent* e) override; void mouseMoveEvent(QMouseEvent* e) override; void keyPressEvent(QKeyEvent* e) override; void wheelEvent(QWheelEvent *) override; bool eventFilter(QObject* recv, QEvent* e) override; QSize sizeHint() const override; QSize minimumSizeHint() const override; QStyleOptionSpinBox spinBoxOptions() const; QStyleOptionProgressBar progressBarOptions() const; QRect editRect(const QStyleOptionSpinBox& spinBoxOptions) const; QRect progressRect(const QStyleOptionProgressBar& progressBarOptions) const; QRect upButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const; QRect downButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const; int valueForX(int x, Qt::KeyboardModifiers modifiers = Qt::NoModifier) const; virtual QString valueString() const = 0; virtual void setInternalValue(int value) = 0; protected Q_SLOTS: void contextMenuEvent(QContextMenuEvent* event) override; void editLostFocus(); protected: RAbstractSliderSpinBoxPrivate* const d_ptr; }; // --------------------------------------------------------------------------------- class RSliderSpinBox : public RAbstractSliderSpinBox { Q_OBJECT Q_DECLARE_PRIVATE(RSliderSpinBox) Q_PROPERTY( int minimum READ minimum WRITE setMinimum ) Q_PROPERTY( int maximum READ maximum WRITE setMaximum ) public: RSliderSpinBox(QWidget* const parent = 0); ~RSliderSpinBox() override; void setRange(int minimum, int maximum); int minimum() const; void setMinimum(int minimum); int maximum() const; void setMaximum(int maximum); int fastSliderStep() const; void setFastSliderStep(int step); int value() const; void setValue(int value); void setSingleStep(int value); void setPageStep(int value); Q_SIGNALS: void valueChanged(int value); protected: QString valueString() const override; void setInternalValue(int value) override; }; // --------------------------------------------------------------------------------- class RDoubleSliderSpinBox : public RAbstractSliderSpinBox { Q_OBJECT Q_DECLARE_PRIVATE(RDoubleSliderSpinBox) public: RDoubleSliderSpinBox(QWidget* const parent = 0); ~RDoubleSliderSpinBox() override; void setRange(double minimum, double maximum, int decimals = 0); double minimum() const; void setMinimum(double minimum); double maximum() const; void setMaximum(double maximum); double fastSliderStep() const; void setFastSliderStep(double step); double value() const; void setValue(double value); void setSingleStep(double value); Q_SIGNALS: void valueChanged(double value); protected: QString valueString() const override; void setInternalValue(int val) override; }; } // namespace KDcrawIface #endif // RSLIDERSPINBOX_H diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rwidgetutils.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/rwidgetutils.cpp index 35e1cc1266..67c2daf946 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/rwidgetutils.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rwidgetutils.cpp @@ -1,618 +1,618 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2014-09-12 * @brief Simple helper widgets collection * * @author Copyright (C) 2014-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "rwidgetutils.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "libkdcraw_debug.h" namespace KDcrawIface { RActiveLabel::RActiveLabel(const QUrl& url, const QString& imgPath, QWidget* const parent) : QLabel(parent) { setMargin(0); setScaledContents(false); setOpenExternalLinks(true); setTextFormat(Qt::RichText); setFocusPolicy(Qt::NoFocus); setTextInteractionFlags(Qt::LinksAccessibleByMouse); setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); QImage img = QImage(imgPath); updateData(url, img); } RActiveLabel::~RActiveLabel() { } void RActiveLabel::updateData(const QUrl& url, const QImage& img) { QByteArray byteArray; QBuffer buffer(&byteArray); img.save(&buffer, "PNG"); setText(QString::fromLatin1("%2") .arg(url.url()) .arg(QString::fromLatin1("") .arg(QString::fromLatin1(byteArray.toBase64().data())))); } // ------------------------------------------------------------------------------------ RLineWidget::RLineWidget(Qt::Orientation orientation, QWidget* const parent) : QFrame(parent) { setLineWidth(1); setMidLineWidth(0); if (orientation == Qt::Vertical) { setFrameShape(QFrame::VLine); setFrameShadow(QFrame::Sunken); setMinimumSize(2, 0); } else { setFrameShape(QFrame::HLine); setFrameShadow(QFrame::Sunken); setMinimumSize(0, 2); } updateGeometry(); } RLineWidget::~RLineWidget() { } // ------------------------------------------------------------------------------------ RHBox::RHBox(QWidget* const parent) : QFrame(parent) { QHBoxLayout* const layout = new QHBoxLayout(this); layout->setSpacing(0); layout->setMargin(0); setLayout(layout); } RHBox::RHBox(bool /*vertical*/, QWidget* const parent) : QFrame(parent) { QVBoxLayout* const layout = new QVBoxLayout(this); layout->setSpacing(0); layout->setMargin(0); setLayout(layout); } RHBox::~RHBox() { } void RHBox::childEvent(QChildEvent* e) { switch (e->type()) { case QEvent::ChildAdded: { QChildEvent* const ce = static_cast(e); if (ce->child()->isWidgetType()) { QWidget* const w = static_cast(ce->child()); static_cast(layout())->addWidget(w); } break; } case QEvent::ChildRemoved: { QChildEvent* const ce = static_cast(e); if (ce->child()->isWidgetType()) { QWidget* const w = static_cast(ce->child()); static_cast(layout())->removeWidget(w); } break; } default: break; } QFrame::childEvent(e); } QSize RHBox::sizeHint() const { RHBox* const b = const_cast(this); QApplication::sendPostedEvents(b, QEvent::ChildAdded); return QFrame::sizeHint(); } QSize RHBox::minimumSizeHint() const { RHBox* const b = const_cast(this); QApplication::sendPostedEvents(b, QEvent::ChildAdded ); return QFrame::minimumSizeHint(); } void RHBox::setSpacing(int spacing) { layout()->setSpacing(spacing); } void RHBox::setMargin(int margin) { layout()->setMargin(margin); } void RHBox::setStretchFactor(QWidget* const widget, int stretch) { static_cast(layout())->setStretchFactor(widget, stretch); } // ------------------------------------------------------------------------------------ RVBox::RVBox(QWidget* const parent) : RHBox(true, parent) { } RVBox::~RVBox() { } // ------------------------------------------------------------------------------------ class Q_DECL_HIDDEN RAdjustableLabel::Private { public: Private() { emode = Qt::ElideMiddle; } QString ajdText; Qt::TextElideMode emode; }; RAdjustableLabel::RAdjustableLabel(QWidget* const parent) : QLabel(parent), d(new Private) { setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); } RAdjustableLabel::~RAdjustableLabel() { delete d; } void RAdjustableLabel::resizeEvent(QResizeEvent*) { adjustTextToLabel(); } QSize RAdjustableLabel::minimumSizeHint() const { QSize sh = QLabel::minimumSizeHint(); sh.setWidth(-1); return sh; } QSize RAdjustableLabel::sizeHint() const { QFontMetrics fm(fontMetrics()); int maxW = QApplication::desktop()->screenGeometry(this).width() * 3 / 4; int currentW = fm.width(d->ajdText); return (QSize(currentW > maxW ? maxW : currentW, QLabel::sizeHint().height())); } void RAdjustableLabel::setAdjustedText(const QString& text) { d->ajdText = text; if (d->ajdText.isNull()) QLabel::clear(); adjustTextToLabel(); } QString RAdjustableLabel::adjustedText() const { return d->ajdText; } void RAdjustableLabel::setAlignment(Qt::Alignment alignment) { QString tmp(d->ajdText); QLabel::setAlignment(alignment); d->ajdText = tmp; } void RAdjustableLabel::setElideMode(Qt::TextElideMode mode) { d->emode = mode; adjustTextToLabel(); } void RAdjustableLabel::adjustTextToLabel() { QFontMetrics fm(fontMetrics()); QStringList adjustedLines; int lblW = size().width(); bool adjusted = false; Q_FOREACH(const QString& line, d->ajdText.split(QLatin1Char('\n'))) { int lineW = fm.width(line); if (lineW > lblW) { adjusted = true; adjustedLines << fm.elidedText(line, d->emode, lblW); } else { adjustedLines << line; } } if (adjusted) { QLabel::setText(adjustedLines.join(QStringLiteral("\n"))); setToolTip(d->ajdText); } else { QLabel::setText(d->ajdText); setToolTip(QString()); } } // ------------------------------------------------------------------------------------ class Q_DECL_HIDDEN RFileSelector::Private { public: Private() { edit = 0; btn = 0; fdMode = QFileDialog::ExistingFile; fdOptions = QFileDialog::DontUseNativeDialog; } QLineEdit* edit; QPushButton* btn; QFileDialog::FileMode fdMode; QString fdFilter; QString fdTitle; QFileDialog::Options fdOptions; }; RFileSelector::RFileSelector(QWidget* const parent) : RHBox(parent), d(new Private) { d->edit = new QLineEdit(this); d->btn = new QPushButton(i18n("Browse..."), this); setStretchFactor(d->edit, 10); connect(d->btn, SIGNAL(clicked()), this, SLOT(slotBtnClicked())); } RFileSelector::~RFileSelector() { delete d; } QLineEdit* RFileSelector::lineEdit() const { return d->edit; } void RFileSelector::setFileDlgMode(QFileDialog::FileMode mode) { d->fdMode = mode; } void RFileSelector::setFileDlgFilter(const QString& filter) { d->fdFilter = filter; } void RFileSelector::setFileDlgTitle(const QString& title) { d->fdTitle = title; } void RFileSelector::setFileDlgOptions(QFileDialog::Options opts) { d->fdOptions = opts; } void RFileSelector::slotBtnClicked() { if (d->fdMode == QFileDialog::ExistingFiles) { qCDebug(LIBKDCRAW_LOG) << "Multiple selection is not supported"; return; } QFileDialog* const fileDlg = new QFileDialog(this); fileDlg->setOptions(d->fdOptions); fileDlg->setDirectory(QFileInfo(d->edit->text()).dir()); fileDlg->setFileMode(d->fdMode); if (!d->fdFilter.isNull()) fileDlg->setNameFilter(d->fdFilter); if (!d->fdTitle.isNull()) fileDlg->setWindowTitle(d->fdTitle); connect(fileDlg, SIGNAL(urlSelected(QUrl)), this, SIGNAL(signalUrlSelected(QUrl))); emit signalOpenFileDialog(); if (fileDlg->exec() == QDialog::Accepted) { QStringList sel = fileDlg->selectedFiles(); if (!sel.isEmpty()) { d->edit->setText(sel.first()); } } delete fileDlg; } // --------------------------------------------------------------------------------------- WorkingPixmap::WorkingPixmap() { QPixmap pix(QStandardPaths::locate(QStandardPaths::AppDataLocation, QLatin1String("libkdcraw/pics/process-working.png"))); QSize size(22, 22); if (pix.isNull()) { qCWarning(LIBKDCRAW_LOG) << "Invalid pixmap specified."; return; } if (!size.isValid()) { size = QSize(pix.width(), pix.width()); } if (pix.width() % size.width() || pix.height() % size.height()) { qCWarning(LIBKDCRAW_LOG) << "Invalid framesize."; return; } const int rowCount = pix.height() / size.height(); const int colCount = pix.width() / size.width(); m_frames.resize(rowCount * colCount); int pos = 0; for (int row = 0; row < rowCount; ++row) { for (int col = 0; col < colCount; ++col) { QPixmap frm = pix.copy(col * size.width(), row * size.height(), size.width(), size.height()); m_frames[pos++] = frm; } } } WorkingPixmap::~WorkingPixmap() { } bool WorkingPixmap::isEmpty() const { return m_frames.isEmpty(); } QSize WorkingPixmap::frameSize() const { if (isEmpty()) { qCWarning(LIBKDCRAW_LOG) << "No frame loaded."; return QSize(); } return m_frames[0].size(); } int WorkingPixmap::frameCount() const { return m_frames.size(); } QPixmap WorkingPixmap::frameAt(int index) const { if (isEmpty()) { qCWarning(LIBKDCRAW_LOG) << "No frame loaded."; return QPixmap(); } return m_frames.at(index); } // ------------------------------------------------------------------------------------ class Q_DECL_HIDDEN RColorSelector::Private { public: Private() { } QColor color; }; RColorSelector::RColorSelector(QWidget* const parent) : QPushButton(parent), d(new Private) { connect(this, SIGNAL(clicked()), this, SLOT(slotBtnClicked())); } RColorSelector::~RColorSelector() { delete d; } void RColorSelector::setColor(const QColor& color) { if (color.isValid()) { d->color = color; update(); } } QColor RColorSelector::color() const { return d->color; } void RColorSelector::slotBtnClicked() { QColor color = QColorDialog::getColor(d->color); if (color.isValid()) { setColor(color); emit signalColorSelected(color); } } void RColorSelector::paintEvent(QPaintEvent*) { QPainter painter(this); QStyle* const style = QWidget::style(); QStyleOptionButton opt; opt.initFrom(this); opt.state |= isDown() ? QStyle::State_Sunken : QStyle::State_Raised; opt.features = QStyleOptionButton::None; opt.icon = QIcon(); opt.text.clear(); style->drawControl(QStyle::CE_PushButtonBevel, &opt, &painter, this); QRect labelRect = style->subElementRect(QStyle::SE_PushButtonContents, &opt, this); int shift = style->pixelMetric(QStyle::PM_ButtonMargin, &opt, this) / 2; labelRect.adjust(shift, shift, -shift, -shift); int x, y, w, h; labelRect.getRect(&x, &y, &w, &h); if (isChecked() || isDown()) { x += style->pixelMetric(QStyle::PM_ButtonShiftHorizontal, &opt, this); y += style->pixelMetric(QStyle::PM_ButtonShiftVertical, &opt, this); } QColor fillCol = isEnabled() ? d->color : palette().color(backgroundRole()); qDrawShadePanel(&painter, x, y, w, h, palette(), true, 1, 0); if (fillCol.isValid()) { const QRect rect(x + 1, y + 1, w - 2, h - 2); if (fillCol.alpha() < 255) { QPixmap chessboardPattern(16, 16); QPainter patternPainter(&chessboardPattern); patternPainter.fillRect(0, 0, 8, 8, Qt::black); patternPainter.fillRect(8, 8, 8, 8, Qt::black); patternPainter.fillRect(0, 8, 8, 8, Qt::white); patternPainter.fillRect(8, 0, 8, 8, Qt::white); patternPainter.end(); painter.fillRect(rect, QBrush(chessboardPattern)); } painter.fillRect(rect, fillCol); } if (hasFocus()) { QRect focusRect = style->subElementRect(QStyle::SE_PushButtonFocusRect, &opt, this); QStyleOptionFocusRect focusOpt; focusOpt.init(this); focusOpt.rect = focusRect; focusOpt.backgroundColor = palette().background().color(); style->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpt, &painter, this); } } } // namespace KDcrawIface diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rwidgetutils.h b/plugins/impex/raw/3rdparty/libkdcraw/src/rwidgetutils.h index d17b0690e5..14f8861350 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/rwidgetutils.h +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rwidgetutils.h @@ -1,256 +1,256 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2014-09-12 * @brief Simple helpher widgets collection * * @author Copyright (C) 2014-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef RWIDGETUTILS_H #define RWIDGETUTILS_H // Qt includes #include #include #include #include #include #include #include #include #include #include // Local includes namespace KDcrawIface { /** A widget to host an image into a label with an active url which can be * open to default web browser using simple mouse click. */ class RActiveLabel : public QLabel { Q_OBJECT public: explicit RActiveLabel(const QUrl& url=QUrl(), const QString& imgPath=QString(), QWidget* const parent=0); ~RActiveLabel() override; void updateData(const QUrl& url, const QImage& img); }; // ------------------------------------------------------------------------------------ /** * A widget to show an horizontal or vertical line separator **/ class RLineWidget : public QFrame { Q_OBJECT public: explicit RLineWidget(Qt::Orientation orientation, QWidget* const parent=0); ~RLineWidget() override; }; // ------------------------------------------------------------------------------------ /** An Horizontal widget to host children widgets */ class RHBox : public QFrame { Q_OBJECT Q_DISABLE_COPY(RHBox) public: explicit RHBox(QWidget* const parent=0); ~RHBox() override; void setMargin(int margin); void setSpacing(int space); void setStretchFactor(QWidget* const widget, int stretch); QSize sizeHint() const override; QSize minimumSizeHint() const override; protected: RHBox(bool vertical, QWidget* const parent); void childEvent(QChildEvent* e) override; }; // ------------------------------------------------------------------------------------ /** A Vertical widget to host children widgets */ class RVBox : public RHBox { Q_OBJECT Q_DISABLE_COPY(RVBox) public: explicit RVBox(QWidget* const parent=0); ~RVBox() override; }; // ------------------------------------------------------------------------------------ /** A label to show text adjusted to widget size */ class RAdjustableLabel : public QLabel { Q_OBJECT public: explicit RAdjustableLabel(QWidget* const parent=0); ~RAdjustableLabel() override; QSize minimumSizeHint() const override; QSize sizeHint() const override; void setAlignment(Qt::Alignment align); void setElideMode(Qt::TextElideMode mode); QString adjustedText() const; public Q_SLOTS: void setAdjustedText(const QString& text=QString()); private: void resizeEvent(QResizeEvent*) override; void adjustTextToLabel(); // Disabled methods from QLabel QString text() const { return QString(); }; // Use adjustedText() instead. void setText(const QString&) {}; // Use setAdjustedText(text) instead. void clear() {}; // Use setdjustedText(QString()) instead. private: class Private; Private* const d; }; // ------------------------------------------------------------------------------------ /** A widget to chosse a single local file or path. * Use line edit and file dialog properties to customize operation modes. */ class RFileSelector : public RHBox { Q_OBJECT public: explicit RFileSelector(QWidget* const parent=0); ~RFileSelector() override; QLineEdit* lineEdit() const; void setFileDlgMode(QFileDialog::FileMode mode); void setFileDlgFilter(const QString& filter); void setFileDlgTitle(const QString& title); void setFileDlgOptions(QFileDialog::Options opts); Q_SIGNALS: void signalOpenFileDialog(); void signalUrlSelected(const QUrl&); private Q_SLOTS: void slotBtnClicked(); private: class Private; Private* const d; }; // -------------------------------------------------------------------------------------- /** A widget to draw progress wheel indicator over thumbnails. */ class WorkingPixmap { public: explicit WorkingPixmap(); ~WorkingPixmap(); bool isEmpty() const; QSize frameSize() const; int frameCount() const; QPixmap frameAt(int index) const; private: QVector m_frames; }; // ------------------------------------------------------------------------------------ /** A widget to chosse a color from a palette. */ class RColorSelector : public QPushButton { Q_OBJECT public: explicit RColorSelector(QWidget* const parent=0); ~RColorSelector() override; void setColor(const QColor& color); QColor color() const; Q_SIGNALS: void signalColorSelected(const QColor&); private Q_SLOTS: void slotBtnClicked(); private: void paintEvent(QPaintEvent*) override; private: class Private; Private* const d; }; } // namespace KDcrawIface #endif // RWIDGETUTILS_H diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.cpp index 2ff49314fc..ffa40dee49 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.cpp @@ -1,196 +1,195 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2008-08-21 * @brief a combo box with a width not depending of text * content size * * @author Copyright (C) 2006-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2008 by Andi Clemens * andi dot clemens at googlemail dot com * @author Copyright (C) 2005 by Tom Albers * tomalbers at kde dot nl * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "squeezedcombobox.h" // Qt includes #include #include #include #include #include namespace KDcrawIface { class Q_DECL_HIDDEN SqueezedComboBox::Private { public: Private() { timer = 0; } QMap originalItems; QTimer* timer; }; SqueezedComboBox::SqueezedComboBox(QWidget* const parent, const char* name) : QComboBox(parent), d(new Private) { setObjectName(name); setMinimumWidth(100); d->timer = new QTimer(this); d->timer->setSingleShot(true); connect(d->timer, &QTimer::timeout, this, &SqueezedComboBox::slotTimeOut); connect(this, static_cast(&SqueezedComboBox::activated), this, &SqueezedComboBox::slotUpdateToolTip); } SqueezedComboBox::~SqueezedComboBox() { d->originalItems.clear(); delete d->timer; delete d; } bool SqueezedComboBox::contains(const QString& text) const { if (text.isEmpty()) return false; for (QMap::const_iterator it = d->originalItems.constBegin() ; it != d->originalItems.constEnd(); ++it) { if (it.value() == text) return true; } return false; } QSize SqueezedComboBox::sizeHint() const { ensurePolished(); QFontMetrics fm = fontMetrics(); int maxW = count() ? 18 : 7 * fm.width(QChar('x')) + 18; int maxH = qMax( fm.lineSpacing(), 14 ) + 2; QStyleOptionComboBox options; options.initFrom(this); - return style()->sizeFromContents(QStyle::CT_ComboBox, &options, - QSize(maxW, maxH), this).expandedTo(QApplication::globalStrut()); + return style()->sizeFromContents(QStyle::CT_ComboBox, &options, QSize(maxW, maxH), this); } void SqueezedComboBox::insertSqueezedItem(const QString& newItem, int index, const QVariant& userData) { d->originalItems[index] = newItem; QComboBox::insertItem(index, squeezeText(newItem), userData); // if this is the first item, set the tooltip. if (index == 0) slotUpdateToolTip(0); } void SqueezedComboBox::insertSqueezedList(const QStringList& newItems, int index) { for(QStringList::const_iterator it = newItems.constBegin() ; it != newItems.constEnd() ; ++it) { insertSqueezedItem(*it, index); index++; } } void SqueezedComboBox::addSqueezedItem(const QString& newItem, const QVariant& userData) { insertSqueezedItem(newItem, count(), userData); } void SqueezedComboBox::setCurrent(const QString& itemText) { QString squeezedText = squeezeText(itemText); qint32 itemIndex = findText(squeezedText); if (itemIndex >= 0) setCurrentIndex(itemIndex); } void SqueezedComboBox::resizeEvent(QResizeEvent *) { d->timer->start(200); } void SqueezedComboBox::slotTimeOut() { for (QMap::iterator it = d->originalItems.begin() ; it != d->originalItems.end(); ++it) { setItemText( it.key(), squeezeText( it.value() ) ); } } QString SqueezedComboBox::squeezeText(const QString& original) const { // not the complete widgetSize is usable. Need to compensate for that. int widgetSize = width()-30; QFontMetrics fm( fontMetrics() ); // If we can fit the full text, return that. if (fm.width(original) < widgetSize) return(original); // We need to squeeze. QString sqItem = original; // prevent empty return value; widgetSize = widgetSize-fm.width("..."); for (int i = 0 ; i != original.length(); ++i) { if ((int)fm.width(original.right(i)) > widgetSize) { sqItem = QString(original.left(i) + "..."); break; } } return sqItem; } void SqueezedComboBox::slotUpdateToolTip(int index) { setToolTip(d->originalItems[index]); } QString SqueezedComboBox::currentUnsqueezedText() const { int curItem = currentIndex(); return d->originalItems[curItem]; } QString SqueezedComboBox::item(int index) const { return d->originalItems[index]; } } // namespace KDcrawIface diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.h b/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.h index 7a482e52c2..906b32b664 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.h +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.h @@ -1,165 +1,165 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2008-08-21 * @brief a combo box with a width not depending of text * content size * * @author Copyright (C) 2006-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2008 by Andi Clemens * andi dot clemens at googlemail dot com * @author Copyright (C) 2005 by Tom Albers * tomalbers at kde dot nl * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef SQUEEZEDCOMBOBOX_H #define SQUEEZEDCOMBOBOX_H // Qt includes #include // Local includes namespace KDcrawIface { /** @class SqueezedComboBox * * This widget is a QComboBox, but then a little bit * different. It only shows the right part of the items * depending on de size of the widget. When it is not * possible to show the complete item, it will be shortened * and "..." will be prepended. */ class SqueezedComboBox : public QComboBox { Q_OBJECT public: /** * Constructor * @param parent parent widget * @param name name to give to the widget */ explicit SqueezedComboBox(QWidget* const parent = 0, const char* name = 0 ); /** * destructor */ ~SqueezedComboBox() override; /** * * Returns true if the combobox contains the original (not-squeezed) * version of text. * @param text the original (not-squeezed) text to check for */ bool contains(const QString& text) const; /** * This inserts a item to the list. See QComboBox::insertItem() * for details. Please do not use QComboBox::insertItem() to this * widget, as that will fail. * @param newItem the original (long version) of the item which needs * to be added to the combobox * @param index the position in the widget. * @param userData custom meta-data assigned to new item. */ void insertSqueezedItem(const QString& newItem, int index, const QVariant& userData=QVariant()); /** * This inserts items to the list. See QComboBox::insertItems() * for details. Please do not use QComboBox:: insertItems() to this * widget, as that will fail. * @param newItems the originals (long version) of the items which needs * to be added to the combobox * @param index the position in the widget. */ void insertSqueezedList(const QStringList& newItems, int index); /** * Append an item. * @param newItem the original (long version) of the item which needs * to be added to the combobox * @param userData custom meta-data assigned to new item. */ void addSqueezedItem(const QString& newItem, const QVariant& userData=QVariant()); /** * Set the current item to the one matching the given text. * * @param itemText the original (long version) of the item text */ void setCurrent(const QString& itemText); /** * This method returns the full text (not squeezed) of the currently * highlighted item. * @return full text of the highlighted item */ QString currentUnsqueezedText() const; /** * This method returns the full text (not squeezed) for the index. * @param index the position in the widget. * @return full text of the item */ QString item(int index) const; /** * Sets the sizeHint() of this widget. */ QSize sizeHint() const override; private Q_SLOTS: void slotTimeOut(); void slotUpdateToolTip(int index); private: void resizeEvent(QResizeEvent*) override; QString squeezeText(const QString& original) const; // Prevent these from being used. QString currentText() const; void setCurrentText(const QString& itemText); void insertItem(const QString& text); void insertItem(qint32 index, const QString& text); void insertItem(int index, const QIcon& icon, const QString& text, const QVariant& userData=QVariant()); void insertItems(int index, const QStringList& list); void addItem(const QString& text); void addItem(const QIcon& icon, const QString& text, const QVariant& userData=QVariant()); void addItems(const QStringList& texts); QString itemText(int index) const; private: class Private; Private* const d; }; } // namespace KDcrawIface #endif // SQUEEZEDCOMBOBOX_H diff --git a/plugins/impex/raw/3rdparty/libkdcraw/tests/libinfo.cpp b/plugins/impex/raw/3rdparty/libkdcraw/tests/libinfo.cpp index 84c194143a..65d4267c19 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/tests/libinfo.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/tests/libinfo.cpp @@ -1,49 +1,49 @@ /** =========================================================== * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2013-09-07 * @brief a command line tool to show libkdcraw info * * @author Copyright (C) 2013 by Gilles Caulier * caulier dot gilles at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ // Qt includes #include #include // Local includes #include using namespace KDcrawIface; int main(int /*argc*/, char** /*argv*/) { qDebug() << "Libkdcraw version : " << KDcraw::version(), qDebug() << "Libraw version : " << KDcraw::librawVersion(); qDebug() << "Use OpenMP : " << KDcraw::librawUseGomp(); qDebug() << "Use RawSpeed : " << KDcraw::librawUseRawSpeed(); qDebug() << "Use GPL2 Pack : " << KDcraw::librawUseGPL2DemosaicPack(); qDebug() << "Use GPL3 Pack : " << KDcraw::librawUseGPL3DemosaicPack(); qDebug() << "Raw files list : " << KDcraw::rawFilesList(); qDebug() << "Raw files version : " << KDcraw::rawFilesVersion(); qDebug() << "Supported camera : " << KDcraw::supportedCamera(); return 0; } diff --git a/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/actionthread.cpp b/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/actionthread.cpp index 48fd250504..6a6e1327f9 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/actionthread.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/actionthread.cpp @@ -1,171 +1,171 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date : 2014-10-17 * @brief : a class to manage Raw to Png conversion using threads * * @author Copyright (C) 2011-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2014 by Veaceslav Munteanu * veaceslav dot munteanu90 at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "actionthread.h" // Qt includes #include #include #include // Local includes #include "kdcraw.h" #include "ractionjob.h" class Task : public RActionJob { public: Task() : RActionJob() { } RawDecodingSettings settings; QString errString; QUrl fileUrl; protected: void run() { emit signalStarted(); QImage image; if (m_cancel) return; emit signalProgress(20); KDcraw rawProcessor; if (m_cancel) return; emit signalProgress(30); QFileInfo input(fileUrl.toLocalFile()); QString fullFilePath(input.baseName() + QString::fromLatin1(".full.png")); QFileInfo fullOutput(fullFilePath); if (m_cancel) return; emit signalProgress(40); if (!rawProcessor.loadFullImage(image, fileUrl.toLocalFile(), settings)) { errString = QString::fromLatin1("raw2png: Loading full RAW image failed. Aborted..."); return; } if (m_cancel) return; emit signalProgress(60); qDebug() << "raw2png: Saving full RAW image to " << fullOutput.fileName() << " size (" << image.width() << "x" << image.height() << ")"; if (m_cancel) return; emit signalProgress(80); image.save(fullFilePath, "PNG"); emit signalDone(); } }; // ---------------------------------------------------------------------------------------------------- ActionThread::ActionThread(QObject* const parent) : RActionThreadBase(parent) { } ActionThread::~ActionThread() { } void ActionThread::convertRAWtoPNG(const QList& list, const RawDecodingSettings& settings, int priority) { RJobCollection collection; Q_FOREACH (const QUrl& url, list) { Task* const job = new Task(); job->fileUrl = url; job->settings = settings; connect(job, SIGNAL(signalStarted()), this, SLOT(slotJobStarted())); connect(job, SIGNAL(signalProgress(int)), this, SLOT(slotJobProgress(int))); connect(job, SIGNAL(signalDone()), this, SLOT(slotJobDone())); collection.insert(job, priority); qDebug() << "Appending file to process " << url; } appendJobs(collection); } void ActionThread::slotJobDone() { Task* const task = dynamic_cast(sender()); if (!task) return; if (task->errString.isEmpty()) { emit finished(task->fileUrl); } else { emit failed(task->fileUrl, task->errString); } } void ActionThread::slotJobProgress(int p) { Task* const task = dynamic_cast(sender()); if (!task) return; emit progress(task->fileUrl, p); } void ActionThread::slotJobStarted() { Task* const task = dynamic_cast(sender()); if (!task) return; emit starting(task->fileUrl); -} \ No newline at end of file +} diff --git a/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/actionthread.h b/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/actionthread.h index 526815c2e9..f0a7de9654 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/actionthread.h +++ b/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/actionthread.h @@ -1,66 +1,66 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date : 2014-10-17 * @brief : a class to manage Raw to Png conversion using threads * * @author Copyright (C) 2011-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2014 by Veaceslav Munteanu * veaceslav dot munteanu90 at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef ACTIONTHREAD_H #define ACTIONTHREAD_H // Qt includes #include // Libkdcraw includes #include "ractionthreadbase.h" #include "rawdecodingsettings.h" using namespace KDcrawIface; class ActionThread : public RActionThreadBase { Q_OBJECT public: ActionThread(QObject* const parent); ~ActionThread(); void convertRAWtoPNG(const QList& list, const RawDecodingSettings& settings, int priority=0); Q_SIGNALS: void starting(const QUrl& url); void finished(const QUrl& url); void failed(const QUrl& url, const QString& err); void progress(const QUrl& url, int percent); private Q_SLOTS: void slotJobDone(); void slotJobProgress(int); void slotJobStarted(); }; #endif /* ACTIONTHREAD_H */ diff --git a/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/main.cpp b/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/main.cpp index 38d777a0e1..2b44cef00f 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/main.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/main.cpp @@ -1,78 +1,78 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date : 2011-12-28 * @brief : test for implementation of threadWeaver api * * @author Copyright (C) 2011-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2014 by Veaceslav Munteanu * veaceslav dot munteanu90 at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ // Qt includes #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "rawfiles.h" #include "processordlg.h" int main(int argc, char* argv[]) { QApplication app(argc, argv); QList list; if (argc <= 1) { QString filter = i18n("Raw Files") + QString::fromLatin1(" (%1)").arg(QString::fromLatin1(raw_file_extentions)); qDebug() << filter; QStringList files = QFileDialog::getOpenFileNames(0, i18n("Select RAW files to process"), QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).first(), filter); Q_FOREACH (QString f, files) list.append(QUrl::fromLocalFile(f)); } else { for (int i = 1 ; i < argc ; i++) list.append(QUrl::fromLocalFile(QString::fromLocal8Bit(argv[i]))); } if (!list.isEmpty()) { ProcessorDlg* const dlg = new ProcessorDlg(list); dlg->show(); app.exec(); } return 0; } diff --git a/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/processordlg.cpp b/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/processordlg.cpp index dcbb7fbb79..57fa8471e9 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/processordlg.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/processordlg.cpp @@ -1,280 +1,280 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date : 2014-10-17 * @brief : test for implementation of threadWeaver api * * @author Copyright (C) 2011-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2014 by Veaceslav Munteanu * veaceslav dot munteanu90 at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "processordlg.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "actionthread.h" #include "dcrawsettingswidget.h" #include "rnuminput.h" class ProcessorDlg::Private { public: Private() { count = 0; page = 0; items = 0; buttons = 0; progressView = 0; usedCore = 0; thread = 0; settings = 0; } int count; QWidget* page; QLabel* items; QDialogButtonBox* buttons; QScrollArea* progressView; QList list; DcrawSettingsWidget* settings; RIntNumInput* usedCore; ActionThread* thread; }; ProcessorDlg::ProcessorDlg(const QList& list) : QDialog(0), d(new Private) { setModal(false); setWindowTitle(i18n("Convert RAW files To PNG")); d->buttons = new QDialogButtonBox(QDialogButtonBox::Apply | QDialogButtonBox::Close, this); d->thread = new ActionThread(this); d->list = list; d->count = d->list.count(); qDebug() << d->list; d->page = new QWidget(this); QVBoxLayout* const vbx = new QVBoxLayout(this); vbx->addWidget(d->page); vbx->addWidget(d->buttons); setLayout(vbx); int cpu = d->thread->maximumNumberOfThreads(); QGridLayout* const grid = new QGridLayout(d->page); QLabel* const pid = new QLabel(i18n("PID: %1", QCoreApplication::applicationPid()), this); QLabel* const core = new QLabel(i18n("CPU Cores: %1", cpu), this); QWidget* const hbox = new QWidget(this); d->items = new QLabel(this); QHBoxLayout* const hlay = new QHBoxLayout(hbox); QLabel* const coresLabel = new QLabel(i18n("Cores to use: "), this); d->usedCore = new RIntNumInput(this); d->usedCore->setRange(1, cpu, 1); d->usedCore->setDefaultValue(cpu); hlay->addWidget(coresLabel); hlay->addWidget(d->usedCore); hlay->setContentsMargins(QMargins()); d->progressView = new QScrollArea(this); QWidget* const progressbox = new QWidget(d->progressView->viewport()); QVBoxLayout* const progressLay = new QVBoxLayout(progressbox); d->progressView->setWidget(progressbox); d->progressView->setWidgetResizable(true); d->settings = new DcrawSettingsWidget(this); grid->addWidget(pid, 0, 0, 1, 1); grid->addWidget(core, 1, 0, 1, 1); grid->addWidget(hbox, 2, 0, 1, 1); grid->addWidget(d->items, 3, 0, 1, 1); grid->addWidget(d->progressView, 4, 0, 1, 1); grid->addWidget(d->settings, 0, 1, 5, 1); Q_FOREACH (const QUrl& url, d->list) { QProgressBar* const bar = new QProgressBar(progressbox); QString file = url.toLocalFile(); QFileInfo fi(file); bar->setMaximum(100); bar->setMinimum(0); bar->setValue(100); bar->setObjectName(file); bar->setFormat(fi.fileName()); progressLay->addWidget(bar); } progressLay->addStretch(); QPushButton* const applyBtn = d->buttons->button(QDialogButtonBox::Apply); QPushButton* const cancelBtn = d->buttons->button(QDialogButtonBox::Close); connect(applyBtn, SIGNAL(clicked()), this, SLOT(slotStart())); connect(cancelBtn, SIGNAL(clicked()), this, SLOT(slotStop())); connect(d->thread, SIGNAL(starting(QUrl)), this, SLOT(slotStarting(QUrl))); connect(d->thread, SIGNAL(finished(QUrl)), this, SLOT(slotFinished(QUrl))); connect(d->thread, SIGNAL(failed(QUrl,QString)), this, SLOT(slotFailed(QUrl,QString))); connect(d->thread, SIGNAL(progress(QUrl,int)), this, SLOT(slotProgress(QUrl,int))); updateCount(); resize(500, 400); } ProcessorDlg::~ProcessorDlg() { delete d; } void ProcessorDlg::updateCount() { d->items->setText(i18n("Files to process : %1", d->count)); } void ProcessorDlg::slotStart() { if (d->list.isEmpty()) return; d->buttons->button(QDialogButtonBox::Apply)->setDisabled(true); d->usedCore->setDisabled(true); d->settings->setDisabled(true); d->thread->setMaximumNumberOfThreads(d->usedCore->value()); d->thread->convertRAWtoPNG(d->list, d->settings->settings()); d->thread->start(); } void ProcessorDlg::slotStop() { d->thread->cancel(); reject(); } QProgressBar* ProcessorDlg::findProgressBar(const QUrl& url) const { QList bars = findChildren(); Q_FOREACH (QProgressBar* const b, bars) { if (b->objectName() == url.toLocalFile()) { return b; } } qWarning() << "Cannot found relevant progress bar for " << url.toLocalFile(); return 0; } void ProcessorDlg::slotStarting(const QUrl& url) { qDebug() << "Start to process item " << url.toLocalFile(); QProgressBar* const b = findProgressBar(url); if (b) { d->progressView->ensureWidgetVisible(b); b->setMinimum(0); b->setMaximum(100); b->setValue(0); } } void ProcessorDlg::slotProgress(const QUrl& url,int p) { qDebug() << "Processing item " << url.toLocalFile() << " : " << p << " %";; QProgressBar* const b = findProgressBar(url); if (b) { b->setMinimum(0); b->setMaximum(100); b->setValue(p); } } void ProcessorDlg::slotFinished(const QUrl& url) { qDebug() << "Completed item " << url.toLocalFile(); QProgressBar* const b = findProgressBar(url); if (b) { b->setMinimum(0); b->setMaximum(100); b->setValue(100); b->setFormat(i18n("Done")); d->count--; updateCount(); } } void ProcessorDlg::slotFailed(const QUrl& url, const QString& err) { qDebug() << "Failed to complete item " << url.toLocalFile(); QProgressBar* const b = findProgressBar(url); if (b) { b->setMinimum(0); b->setMaximum(100); b->setValue(100); b->setFormat(err); d->count--; updateCount(); } } diff --git a/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/processordlg.h b/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/processordlg.h index eca8578134..ae37ef5049 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/processordlg.h +++ b/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/processordlg.h @@ -1,67 +1,67 @@ /** =========================================================== * @file * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date : 2014-10-17 * @brief : test for implementation of threadWeaver api * * @author Copyright (C) 2011-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2014 by Veaceslav Munteanu * veaceslav dot munteanu90 at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef PROCESSOR_DLG_H #define PROCESSOR_DLG_H // KDE includes #include #include #include class QProgressBar; class ProcessorDlg : public QDialog { Q_OBJECT public: ProcessorDlg(const QList &list); ~ProcessorDlg(); private : QProgressBar* findProgressBar(const QUrl& url) const; void updateCount(); private Q_SLOTS: void slotStart(); void slotStop(); void slotStarting(const QUrl&); void slotProgress(const QUrl&, int); void slotFinished(const QUrl&); void slotFailed(const QUrl&, const QString&); private: class Private; Private* const d; }; #endif // PROCESSOR_DLG_H diff --git a/plugins/impex/raw/3rdparty/libkdcraw/tests/raw2png.cpp b/plugins/impex/raw/3rdparty/libkdcraw/tests/raw2png.cpp index a182950681..827f43d35a 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/tests/raw2png.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/tests/raw2png.cpp @@ -1,138 +1,138 @@ /** =========================================================== * * This file is a part of digiKam project - * http://www.digikam.org + * https://www.digikam.org * * @date 2008-15-09 * @brief a command line tool to convert RAW file to PNG * * @author Copyright (C) 2008-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ // Qt includes #include #include #include #include // Local includes #include #include using namespace KDcrawIface; int main(int argc, char** argv) { if(argc != 2) { qDebug() << "raw2png - RAW Camera Image to PNG Converter"; qDebug() << "Usage: "; return -1; } QString filePath = QString::fromLatin1(argv[1]); QFileInfo input(filePath); QString previewFilePath(input.baseName() + QString::QString::fromLatin1(".preview.png")); QFileInfo previewOutput(previewFilePath); QString halfFilePath(input.baseName() + QString::fromLatin1(".half.png")); QFileInfo halfOutput(halfFilePath); QString fullFilePath(input.baseName() + QString::fromLatin1(".full.png")); QFileInfo fullOutput(fullFilePath); QImage image; DcrawInfoContainer identify; // ----------------------------------------------------------- qDebug() << "raw2png: Identify RAW image from " << input.fileName(); KDcraw rawProcessor; if (!rawProcessor.rawFileIdentify(identify, filePath)) { qDebug() << "raw2png: Idendify RAW image failed. Aborted..."; return -1; } int width = identify.imageSize.width(); int height = identify.imageSize.height(); qDebug() << "raw2png: Raw image info:"; qDebug() << "--- Date: " << identify.dateTime.toString(Qt::ISODate); qDebug() << "--- Make: " << identify.make; qDebug() << "--- Model: " << identify.model; qDebug() << "--- Size: " << width << "x" << height; qDebug() << "--- Filter: " << identify.filterPattern; qDebug() << "--- Colors: " << identify.rawColors; // ----------------------------------------------------------- qDebug() << "raw2png: Loading RAW image preview"; if (!rawProcessor.loadRawPreview(image, filePath)) { qDebug() << "raw2png: Loading RAW image preview failed. Aborted..."; return -1; } qDebug() << "raw2png: Saving preview image to " << previewOutput.fileName() << " size (" << image.width() << "x" << image.height() << ")"; image.save(previewFilePath, "PNG"); // ----------------------------------------------------------- qDebug() << "raw2png: Loading half RAW image"; image = QImage(); if (!rawProcessor.loadHalfPreview(image, filePath)) { qDebug() << "raw2png: Loading half RAW image failed. Aborted..."; return -1; } qDebug() << "raw2png: Saving half image to " << halfOutput.fileName() << " size (" << image.width() << "x" << image.height() << ")"; image.save(halfFilePath, "PNG"); // ----------------------------------------------------------- qDebug() << "raw2png: Loading full RAW image"; image = QImage(); RawDecodingSettings settings; settings.halfSizeColorImage = false; settings.sixteenBitsImage = false; settings.RGBInterpolate4Colors = false; settings.RAWQuality = RawDecodingSettings::BILINEAR; if (!rawProcessor.loadFullImage(image, filePath, settings)) { qDebug() << "raw2png: Loading full RAW image failed. Aborted..."; return -1; } qDebug() << "raw2png: Saving full RAW image to " << fullOutput.fileName() << " size (" << image.width() << "x" << image.height() << ")"; image.save(fullFilePath, "PNG"); return 0; } diff --git a/plugins/impex/svg/tests/CMakeLists.txt b/plugins/impex/svg/tests/CMakeLists.txt index 94fcb20c21..10e9d1a1bc 100644 --- a/plugins/impex/svg/tests/CMakeLists.txt +++ b/plugins/impex/svg/tests/CMakeLists.txt @@ -1,11 +1,11 @@ set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_SOURCE_DIR}/sdk/tests) include(ECMAddTests) macro_add_unittest_definitions() +include(KritaAddBrokenUnitTest) -ecm_add_test( - kis_svg_test.cpp +krita_add_broken_unit_test(kis_svg_test.cpp TEST_NAME KisSvgTest LINK_LIBRARIES kritaui Qt5::Test NAME_PREFIX "plugins-impex-" ) diff --git a/plugins/impex/xcf/3rdparty/xcftools/utils.c b/plugins/impex/xcf/3rdparty/xcftools/utils.c index aa8ba263f9..a905b5bbb1 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/utils.c +++ b/plugins/impex/xcf/3rdparty/xcftools/utils.c @@ -1,178 +1,178 @@ /* Generic support functions for Xcftools * * This file was written by Henning Makholm * It is hereby in the public domain. * * In jurisdictions that do not recognise grants of copyright to the * public domain: I, the author and (presumably, in those jurisdictions) * copyright holder, hereby permit anyone to distribute and use this code, * in source code or binary form, with or without modifications. This * permission is world-wide and irrevocable. * * Of course, I will not be liable for any errors or shortcomings in the * code, since I give it away without asking any compenstations. * * If you use or distribute this code, I would appreciate receiving * credit for writing it, in whichever way you find proper and customary. */ #include "xcftools.h" #include #include #include #include const char *progname = "$0" ; int verboseFlag = 0 ; void vFatalGeneric(int status,const char *format, va_list args) { (void) status; /* mark as unused */ if( format ) { if( *format == '!' ) { vfprintf(stderr,format+1,args); fprintf(stderr,": %s\n",strerror(errno)); } else { vfprintf(stderr,format,args); fputc('\n',stderr); } } /* don't exit here - Krita can't handle errors otherwise */ /* exit(status); */ } void FatalGeneric(int status,const char* format,...) { va_list v; va_start(v,format); if( format ) fprintf(stderr,"%s: ",progname); vFatalGeneric(status,format,v); va_end(v); } void FatalUnexpected(const char* format,...) { va_list v; va_start(v, format); fprintf(stderr,"%s: ",progname); vFatalGeneric(127, format, v); va_end(v); } void FatalBadXCF(const char* format,...) { va_list v; va_start(v,format); fprintf(stderr,"%s: %s:\n ",progname,_("Corrupted or malformed XCF file")); vFatalGeneric(125,format,v) ; va_end(v); } int xcfCheckspace(uint32_t addr,int spaceafter,const char *format,...) { if( xcf_length < spaceafter || addr > xcf_length - spaceafter ) { va_list v; va_start(v,format); fprintf(stderr,"%s: %s\n ",progname,_("Corrupted or truncated XCF file")); fprintf(stderr,"(0x%" PRIXPTR " bytes): ",(uintptr_t)xcf_length); vFatalGeneric(125,format,v) ; va_end(v); return XCF_ERROR; } return XCF_OK; } void FatalUnsupportedXCF(const char* format,...) { va_list v; va_start(v,format); fprintf(stderr,"%s: %s\n ",progname, _("The image contains features not understood by this program:")); vFatalGeneric(123,format,v) ; va_end(v); } void gpl_blurb(void) { fprintf(stderr,PACKAGE_STRING "\n"); fprintf(stderr, _("Type \"%s -h\" to get an option summary.\n"),progname); /* don't exit here - Krita will close otherwise */ /* exit(1) ; */ } /* ******************************************************* */ void * xcfmalloc(size_t size) { void *ptr = malloc(size); if( !ptr ) { FatalUnexpected(_("Out of memory")); return XCF_PTR_EMPTY; } return ptr ; } void xcffree(void *block) { if( xcf_file && (uint8_t*)block >= xcf_file && (uint8_t*)block < xcf_file + xcf_length ) ; else free(block); } /* ******************************************************* */ FILE * openout(const char *name) { FILE *newfile ; if( strcmp(name,"-") == 0 ) return stdout ; newfile = fopen(name,"wb") ; if( newfile == NULL ) { FatalUnexpected(_("!Cannot create file %s"),name); return XCF_PTR_EMPTY; } return newfile ; } int closeout(FILE *f,const char *name) { if( f == NULL ) return XCF_OK; if( fflush(f) == 0 ) { errno = 0 ; if( !ferror(f) ) { if( fclose(f) == 0 ) return XCF_OK; } else if( errno == 0 ) { /* Attempt to coax a valid errno out of the standard library, * following an idea by Bruno Haible - * http://lists.gnu.org/archive/html/bug-gnulib/2003-09/msg00157.html + * https://lists.gnu.org/archive/html/bug-gnulib/2003-09/msg00157.html */ if( fputc('\0', f) != EOF && fflush(f) == 0 ) errno = EIO ; /* Argh, everything succedes. Just call it an I/O error */ } } FatalUnexpected(_("!Error writing file %s"),name); return XCF_ERROR; } diff --git a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h index f7df52e583..cb0a182b75 100644 --- a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h +++ b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h @@ -1,82 +1,82 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_DUPLICATEOP_H_ #define KIS_DUPLICATEOP_H_ #include "kis_brush_based_paintop.h" #include #include #include #include #include #include #include #include "kis_duplicateop_settings.h" class KisPaintInformation; class QPointF; class KisPainter; class KisDuplicateOp : public KisBrushBasedPaintOp { public: KisDuplicateOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image); ~KisDuplicateOp() override; protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; private: qreal minimizeEnergy(const qreal* m, qreal* sol, int w, int h); private: KisImageSP m_image; KisNodeSP m_node; KisDuplicateOpSettingsSP m_settings; KisPaintDeviceSP m_srcdev; KisPaintDeviceSP m_target; - QPointF m_duplicateStart; - bool m_duplicateStartIsSet; + QPointF m_duplicateStart {QPointF(0.0, 0.0)}; + bool m_duplicateStartIsSet {false}; KisPressureSizeOption m_sizeOption; KisPressureOpacityOption m_opacityOption; KisPressureRotationOption m_rotationOption; - bool m_healing; - bool m_perspectiveCorrection; - bool m_moveSourcePoint; - bool m_cloneFromProjection; + bool m_healing {false}; + bool m_perspectiveCorrection {false}; + bool m_moveSourcePoint {false}; + bool m_cloneFromProjection {false}; }; #endif // KIS_DUPLICATEOP_H_ diff --git a/plugins/paintops/hatching/kis_hatching_paintop_settings_widget.cpp b/plugins/paintops/hatching/kis_hatching_paintop_settings_widget.cpp index 856ec17169..a1b67a70af 100644 --- a/plugins/paintops/hatching/kis_hatching_paintop_settings_widget.cpp +++ b/plugins/paintops/hatching/kis_hatching_paintop_settings_widget.cpp @@ -1,138 +1,138 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2010 José Luis Vergara * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_hatching_paintop_settings_widget.h" #include "kis_hatching_options.h" #include "kis_hatching_preferences.h" #include "kis_hatching_paintop_settings.h" #include "kis_hatching_pressure_angle_option.h" #include "kis_hatching_pressure_crosshatching_option.h" #include "kis_hatching_pressure_separation_option.h" #include "kis_hatching_pressure_thickness_option.h" #include #include #include #include #include #include #include #include "kis_texture_option.h" #include #include "kis_pressure_texture_strength_option.h" #include #include KisHatchingPaintOpSettingsWidget:: KisHatchingPaintOpSettingsWidget(QWidget* parent) : KisBrushBasedPaintopOptionWidget(parent) { setPrecisionEnabled(true); //-------Adding widgets to the screen------------ addPaintOpOption(new KisHatchingOptions(), i18n("Hatching options")); addPaintOpOption(new KisHatchingPreferences(), i18n("Hatching preferences")); addPaintOpOption(new KisCompositeOpOption(true), i18n("Blending Mode")); addPaintOpOption(new KisCurveOptionWidget(new KisHatchingPressureSeparationOption(), i18n("0.0"), i18n("1.0")), i18n("Separation")); addPaintOpOption(new KisCurveOptionWidget(new KisHatchingPressureThicknessOption(), i18n("0.0"), i18n("1.0")), i18n("Thickness")); addPaintOpOption(new KisCurveOptionWidget(new KisHatchingPressureAngleOption(), i18n("0.0"), i18n("1.0")), i18n("Angle")); addPaintOpOption(new KisCurveOptionWidget(new KisHatchingPressureCrosshatchingOption(), i18n("0.0"), i18n("1.0")), i18n("Crosshatching")); addPaintOpOption(new KisCurveOptionWidget(new KisPressureOpacityOption(), i18n("Transparent"), i18n("Opaque")), i18n("Opacity")); addPaintOpOption(new KisCurveOptionWidget(new KisPressureSizeOption(), i18n("0%"), i18n("100%")), i18n("Size")); addPaintOpOption(new KisPressureMirrorOptionWidget(), i18n("Mirror")); addPaintOpOption(new KisPaintActionTypeOption(), i18n("Painting Mode")); addPaintOpOption(new KisTextureOption(), i18n("Pattern")); addPaintOpOption(new KisCurveOptionWidget(new KisPressureTextureStrengthOption(), i18n("Weak"), i18n("Strong")), i18n("Strength")); //-----Useful to read first:------ /* Below you will encounter a reasonably correct solution to the problem of changing the default presets of the "BrushTip" popup configuration dialogue. In my (Pentalis) opinion, the best solution is code refactoring (simpler ways to change the defaults). On the meanwhile, copypasting this code won't give your class a charisma penalty. In kis_hatching_paintop_settings.cpp you will find a snippet of code to discover the structure of your XML config tree if you need to edit it at build time like here. */ //---------START ALTERING DEFAULT VALUES----------- //As the name implies, reconfigurationCourier is the KisPropertiesConfigurationSP //we'll use as an intermediary to edit the default settings KisPropertiesConfigurationSP reconfigurationCourier = configuration(); /*xMLAnalyzer is an empty document we'll use to analyze and edit the config string part by part I know the important string is "brush_definition" because I read the tree with the snippet in kis_hatching_paintop_settings.cpp */ QDomDocument xMLAnalyzer; xMLAnalyzer.setContent(reconfigurationCourier->getString("brush_definition")); /*More things I know by reading the XML tree. At this point you can just read it with: dbgKrita << xMLAnalyzer.toString() ; those QDomElements are the way to navigate the XML tree, read - http://doc.qt.nokia.com/latest/qdomdocument.html for more information */ + https://doc.qt.io/qt-5/qdomdocument.html for more information */ QDomElement firstTag = xMLAnalyzer.documentElement(); QDomElement firstTagsChild = firstTag.elementsByTagName("MaskGenerator").item(0).toElement(); // SET THE DEFAULT VALUES firstTag.attributeNode("spacing").setValue("0.4"); firstTagsChild.attributeNode("diameter").setValue("30"); //Write them into the intermediary config file reconfigurationCourier->setProperty("brush_definition", xMLAnalyzer.toString()); KisCubicCurve CurveSize; CurveSize.fromString("0,1;1,0.1;"); //dbgKrita << "\n\n\n" << CurveSize.toString() << "\n\n\n"; QVariant QVCurveSize = QVariant::fromValue(CurveSize); reconfigurationCourier->setProperty("CurveSize", QVCurveSize); setConfiguration(reconfigurationCourier); // Finished. /* Debugging block QMap rofl = QMap(reconfigurationCourier->getProperties()); QMap::const_iterator i; for (i = rofl.constBegin(); i != rofl.constEnd(); ++i) dbgKrita << i.key() << ":" << i.value(); */ } KisHatchingPaintOpSettingsWidget::~ KisHatchingPaintOpSettingsWidget() { } KisPropertiesConfigurationSP KisHatchingPaintOpSettingsWidget::configuration() const { KisHatchingPaintOpSettingsSP config = new KisHatchingPaintOpSettings(); config->setOptionsWidget(const_cast(this)); config->setProperty("paintop", "hatchingbrush"); // XXX: make this a const id string writeConfiguration(config); return config; } diff --git a/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp b/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp index b7dc342b13..b5e87819f8 100644 --- a/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp +++ b/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp @@ -1,336 +1,339 @@ /* * Copyright (c) 2010 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_brush_based_paintop_settings.h" #include #include #include "kis_brush_based_paintop_options_widget.h" #include #include "kis_brush_server.h" #include #include "kis_signals_blocker.h" #include "kis_brush_option.h" #include +#include struct BrushReader { BrushReader(const KisBrushBasedPaintOpSettings *parent) : m_parent(parent) { m_option.readOptionSetting(m_parent); } KisBrushSP brush() { return m_option.brush(); } const KisBrushBasedPaintOpSettings *m_parent; KisBrushOptionProperties m_option; }; struct BrushWriter { BrushWriter(KisBrushBasedPaintOpSettings *parent) : m_parent(parent) { m_option.readOptionSetting(m_parent); } ~BrushWriter() { m_option.writeOptionSetting(m_parent); } KisBrushSP brush() { return m_option.brush(); } KisBrushBasedPaintOpSettings *m_parent; KisBrushOptionProperties m_option; }; KisBrushBasedPaintOpSettings::KisBrushBasedPaintOpSettings() : KisOutlineGenerationPolicy(KisCurrentOutlineFetcher::SIZE_OPTION | KisCurrentOutlineFetcher::ROTATION_OPTION | - KisCurrentOutlineFetcher::MIRROR_OPTION) + KisCurrentOutlineFetcher::MIRROR_OPTION | + KisCurrentOutlineFetcher::SHARPNESS_OPTION) + { } bool KisBrushBasedPaintOpSettings::paintIncremental() { if (hasProperty("PaintOpAction")) { return (enumPaintActionType)getInt("PaintOpAction", WASH) == BUILDUP; } return true; } KisPaintOpSettingsSP KisBrushBasedPaintOpSettings::clone() const { KisPaintOpSettingsSP _settings = KisOutlineGenerationPolicy::clone(); KisBrushBasedPaintOpSettingsSP settings = dynamic_cast(_settings.data()); settings->m_savedBrush = 0; return settings; } KisBrushSP KisBrushBasedPaintOpSettings::brush() const { KisBrushSP brush = m_savedBrush; if (!brush) { BrushReader w(this); brush = w.brush(); m_savedBrush = brush; } return brush; } QPainterPath KisBrushBasedPaintOpSettings::brushOutlineImpl(const KisPaintInformation &info, const OutlineMode &mode, qreal additionalScale) { QPainterPath path; if (mode.isVisible) { KisBrushSP brush = this->brush(); if (!brush) return path; qreal finalScale = brush->scale() * additionalScale; QPainterPath realOutline = brush->outline(); if (mode.forceCircle) { QPainterPath ellipse; ellipse.addEllipse(realOutline.boundingRect()); realOutline = ellipse; } path = outlineFetcher()->fetchOutline(info, this, realOutline, mode, finalScale, brush->angle()); if (mode.showTiltDecoration) { const QPainterPath tiltLine = makeTiltIndicator(info, realOutline.boundingRect().center(), realOutline.boundingRect().width() * 0.5, 3.0); path.addPath(outlineFetcher()->fetchOutline(info, this, tiltLine, mode, finalScale, 0.0, true, realOutline.boundingRect().center().x(), realOutline.boundingRect().center().y())); } } return path; } QPainterPath KisBrushBasedPaintOpSettings::brushOutline(const KisPaintInformation &info, const OutlineMode &mode) { return brushOutlineImpl(info, mode, 1.0); } bool KisBrushBasedPaintOpSettings::isValid() const { QStringList files = getStringList(KisPaintOpUtils::RequiredBrushFilesListTag); files << getString(KisPaintOpUtils::RequiredBrushFileTag); Q_FOREACH (const QString &file, files) { if (!file.isEmpty()) { KisBrushSP brush = KisBrushServer::instance()->brushServer()->resourceByFilename(file); if (!brush) { return false; } } } return true; } bool KisBrushBasedPaintOpSettings::isLoadable() { return (KisBrushServer::instance()->brushServer()->resources().count() > 0); } void KisBrushBasedPaintOpSettings::setAngle(qreal value) { BrushWriter w(this); if (!w.brush()) return; w.brush()->setAngle(value); } qreal KisBrushBasedPaintOpSettings::angle() { return this->brush()->angle(); } void KisBrushBasedPaintOpSettings::setSpacing(qreal value) { BrushWriter w(this); if (!w.brush()) return; w.brush()->setSpacing(value); } qreal KisBrushBasedPaintOpSettings::spacing() { return this->brush()->spacing(); } void KisBrushBasedPaintOpSettings::setAutoSpacing(bool active, qreal coeff) { BrushWriter w(this); if (!w.brush()) return; w.brush()->setAutoSpacing(active, coeff); } bool KisBrushBasedPaintOpSettings::autoSpacingActive() { return this->brush()->autoSpacingActive(); } qreal KisBrushBasedPaintOpSettings::autoSpacingCoeff() { return this->brush()->autoSpacingCoeff(); } void KisBrushBasedPaintOpSettings::setPaintOpSize(qreal value) { BrushWriter w(this); if (!w.brush()) return; w.brush()->setUserEffectiveSize(value); } qreal KisBrushBasedPaintOpSettings::paintOpSize() const { return this->brush()->userEffectiveSize(); } #include #include "kis_paintop_preset.h" #include "kis_paintop_settings_update_proxy.h" QList KisBrushBasedPaintOpSettings::uniformProperties(KisPaintOpSettingsSP settings) { QList props = listWeakToStrong(m_uniformProperties); if (props.isEmpty()) { { KisIntSliderBasedPaintOpPropertyCallback *prop = new KisIntSliderBasedPaintOpPropertyCallback( KisIntSliderBasedPaintOpPropertyCallback::Int, "angle", "Angle", settings, 0); prop->setRange(0, 360); prop->setReadCallback( [](KisUniformPaintOpProperty *prop) { KisBrushBasedPaintOpSettings *s = dynamic_cast(prop->settings().data()); const qreal angleResult = kisRadiansToDegrees(s->angle()); prop->setValue(angleResult); }); prop->setWriteCallback( [](KisUniformPaintOpProperty *prop) { KisBrushBasedPaintOpSettings *s = dynamic_cast(prop->settings().data()); s->setAngle(kisDegreesToRadians(prop->value().toReal())); }); QObject::connect(preset()->updateProxy(), SIGNAL(sigSettingsChanged()), prop, SLOT(requestReadValue())); prop->requestReadValue(); props << toQShared(prop); } { KisUniformPaintOpPropertyCallback *prop = new KisUniformPaintOpPropertyCallback( KisUniformPaintOpPropertyCallback::Bool, "auto_spacing", "Auto Spacing", settings, 0); prop->setReadCallback( [](KisUniformPaintOpProperty *prop) { KisBrushBasedPaintOpSettings *s = dynamic_cast(prop->settings().data()); prop->setValue(s->autoSpacingActive()); }); prop->setWriteCallback( [](KisUniformPaintOpProperty *prop) { KisBrushBasedPaintOpSettings *s = dynamic_cast(prop->settings().data()); s->setAutoSpacing(prop->value().toBool(), s->autoSpacingCoeff()); }); QObject::connect(preset()->updateProxy(), SIGNAL(sigSettingsChanged()), prop, SLOT(requestReadValue())); prop->requestReadValue(); props << toQShared(prop); } { KisDoubleSliderBasedPaintOpPropertyCallback *prop = new KisDoubleSliderBasedPaintOpPropertyCallback( KisDoubleSliderBasedPaintOpPropertyCallback::Double, "spacing", "Spacing", settings, 0); prop->setRange(0.01, 10); prop->setSingleStep(0.01); prop->setExponentRatio(3.0); prop->setReadCallback( [](KisUniformPaintOpProperty *prop) { KisBrushBasedPaintOpSettings *s = dynamic_cast(prop->settings().data()); if (s) { const qreal value = s->autoSpacingActive() ? s->autoSpacingCoeff() : s->spacing(); prop->setValue(value); } }); prop->setWriteCallback( [](KisUniformPaintOpProperty *prop) { KisBrushBasedPaintOpSettings *s = dynamic_cast(prop->settings().data()); if (s) { if (s->autoSpacingActive()) { s->setAutoSpacing(true, prop->value().toReal()); } else { s->setSpacing(prop->value().toReal()); } } }); QObject::connect(preset()->updateProxy(), SIGNAL(sigSettingsChanged()), prop, SLOT(requestReadValue())); prop->requestReadValue(); props << toQShared(prop); } } return KisPaintOpSettings::uniformProperties(settings) + props; } void KisBrushBasedPaintOpSettings::onPropertyChanged() { m_savedBrush.clear(); KisOutlineGenerationPolicy::onPropertyChanged(); } diff --git a/plugins/paintops/libpaintop/kis_current_outline_fetcher.cpp b/plugins/paintops/libpaintop/kis_current_outline_fetcher.cpp index fee3928f66..0bdbe9dc43 100644 --- a/plugins/paintops/libpaintop/kis_current_outline_fetcher.cpp +++ b/plugins/paintops/libpaintop/kis_current_outline_fetcher.cpp @@ -1,162 +1,181 @@ /* * 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_current_outline_fetcher.h" #include "kis_pressure_size_option.h" #include "kis_pressure_rotation_option.h" #include "kis_pressure_mirror_option.h" +#include "kis_pressure_sharpness_option.h" #include #include #include "kis_paintop_settings.h" #include #define NOISY_UPDATE_SPEED 50 // Time in ms for outline updates to noisy brushes struct KisCurrentOutlineFetcher::Private { Private(Options optionsAvailable) : isDirty(true) { if (optionsAvailable & SIZE_OPTION) { sizeOption.reset(new KisPressureSizeOption()); } if (optionsAvailable & ROTATION_OPTION) { rotationOption.reset(new KisPressureRotationOption()); } if (optionsAvailable & MIRROR_OPTION) { mirrorOption.reset(new KisPressureMirrorOption()); } + + if (optionsAvailable & SHARPNESS_OPTION) { + sharpnessOption.reset(new KisPressureSharpnessOption()); + } } QScopedPointer sizeOption; QScopedPointer rotationOption; QScopedPointer mirrorOption; + QScopedPointer sharpnessOption; bool isDirty; QElapsedTimer lastUpdateTime; qreal lastRotationApplied; qreal lastSizeApplied; MirrorProperties lastMirrorApplied; }; KisCurrentOutlineFetcher::KisCurrentOutlineFetcher(Options optionsAvailable) : d(new Private(optionsAvailable)) { d->lastUpdateTime.start(); } KisCurrentOutlineFetcher::~KisCurrentOutlineFetcher() { } void KisCurrentOutlineFetcher::setDirty() { d->isDirty = true; } QPainterPath KisCurrentOutlineFetcher::fetchOutline(const KisPaintInformation &info, const KisPaintOpSettingsSP settings, const QPainterPath &originalOutline, const KisPaintOpSettings::OutlineMode &mode, qreal additionalScale, qreal additionalRotation, bool tilt, qreal tiltcenterx, qreal tiltcentery) const { if (d->isDirty) { if (d->sizeOption) { d->sizeOption->readOptionSetting(settings); d->sizeOption->resetAllSensors(); } if (d->rotationOption) { d->rotationOption->readOptionSetting(settings); d->rotationOption->resetAllSensors(); } if (d->mirrorOption) { d->mirrorOption->readOptionSetting(settings); d->mirrorOption->resetAllSensors(); } + if (d->sharpnessOption) { + d->sharpnessOption->readOptionSetting(settings); + d->sharpnessOption->resetAllSensors(); + } + d->isDirty = false; } qreal scale = additionalScale; qreal rotation = additionalRotation; bool needsUpdate = false; // Randomized rotation at full speed looks noisy, so slow it down if (d->lastUpdateTime.elapsed() > NOISY_UPDATE_SPEED) { needsUpdate = true; d->lastUpdateTime.restart(); } if (d->sizeOption && !tilt && !mode.forceFullSize) { if (!d->sizeOption->isRandom() || needsUpdate) { d->lastSizeApplied = d->sizeOption->apply(info); } scale *= d->lastSizeApplied; } if (d->rotationOption && !tilt) { if (!d->rotationOption->isRandom() || needsUpdate) { d->lastRotationApplied = d->rotationOption->apply(info); } rotation += d->lastRotationApplied; } else if (d->rotationOption && tilt) { rotation += info.canvasRotation() * M_PI / 180.0; } qreal xFlip = 1.0; qreal yFlip = 1.0; if (d->mirrorOption) { if (!d->mirrorOption->isRandom() || needsUpdate) { d->lastMirrorApplied = d->mirrorOption->apply(info); } if (d->lastMirrorApplied.coordinateSystemFlipped) { rotation = 2 * M_PI - rotation; } if (d->lastMirrorApplied.horizontalMirror) { xFlip = -1.0; } if (d->lastMirrorApplied.verticalMirror) { yFlip = -1.0; } } - QTransform rot; rot.rotateRadians(-rotation); QPointF hotSpot = originalOutline.boundingRect().center(); if (tilt) { hotSpot.setX(tiltcenterx); hotSpot.setY(tiltcentery); } + + QPointF pos = info.pos(); + if (d->sharpnessOption) { + qint32 x = 0, y = 0; + qreal subPixelX = 0.0, subPixelY = 0.0; + d->sharpnessOption->apply(info, pos - hotSpot, x, y, subPixelX, subPixelY); + pos = QPointF(x + subPixelX, y + subPixelY) + hotSpot; + } + QTransform T1 = QTransform::fromTranslate(-hotSpot.x(), -hotSpot.y()); - QTransform T2 = QTransform::fromTranslate(info.pos().x(), info.pos().y()); + QTransform T2 = QTransform::fromTranslate(pos.x(), pos.y()); QTransform S = QTransform::fromScale(xFlip * scale, yFlip * scale); return (T1 * rot * S * T2).map(originalOutline); } diff --git a/plugins/paintops/libpaintop/kis_current_outline_fetcher.h b/plugins/paintops/libpaintop/kis_current_outline_fetcher.h index 9877db6d46..412b65a328 100644 --- a/plugins/paintops/libpaintop/kis_current_outline_fetcher.h +++ b/plugins/paintops/libpaintop/kis_current_outline_fetcher.h @@ -1,69 +1,70 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_CURRENT_OUTLINE_FETCHER_H #define __KIS_CURRENT_OUTLINE_FETCHER_H #include #include #include #include #include #include class KisPaintInformation; class PAINTOP_EXPORT KisCurrentOutlineFetcher { public: enum Option { NO_OPTION, SIZE_OPTION, ROTATION_OPTION, - MIRROR_OPTION + MIRROR_OPTION, + SHARPNESS_OPTION }; Q_DECLARE_FLAGS(Options, Option); public: KisCurrentOutlineFetcher(Options optionsAvailable); ~KisCurrentOutlineFetcher(); void setDirty(); QPainterPath fetchOutline(const KisPaintInformation &info, const KisPaintOpSettingsSP settings, const QPainterPath &originalOutline, const KisPaintOpSettings::OutlineMode &mode, qreal additionalScale = 1.0, qreal additionalRotation = 0.0, bool tilt = false, qreal tiltcenterx = 1.0, qreal tiltcentery = 1.0) const; private: Q_DISABLE_COPY(KisCurrentOutlineFetcher); struct Private; const QScopedPointer d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KisCurrentOutlineFetcher::Options); #endif /* __KIS_CURRENT_OUTLINE_FETCHER_H */ diff --git a/plugins/paintops/libpaintop/kis_pressure_texture_strength_option.cpp b/plugins/paintops/libpaintop/kis_pressure_texture_strength_option.cpp index 6597a8de95..0ac4a36e26 100644 --- a/plugins/paintops/libpaintop/kis_pressure_texture_strength_option.cpp +++ b/plugins/paintops/libpaintop/kis_pressure_texture_strength_option.cpp @@ -1,66 +1,32 @@ /* * 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_pressure_texture_strength_option.h" #include KisPressureTextureStrengthOption::KisPressureTextureStrengthOption() : KisCurveOption("Texture/Strength/", KisPaintOpOption::TEXTURE, false) { } double KisPressureTextureStrengthOption::apply(const KisPaintInformation & info) const { if (!isChecked()) return 1.0; return computeSizeLikeValue(info); } - -void KisPressureTextureStrengthOption::readOptionSetting(const KisPropertiesConfigurationSP setting) -{ - KisCurveOption::readOptionSetting(setting); - - /** - * Backward compatibility with Krita < 2.7. - * - * Process the presets created with the old UI, when the - * strength was a part of Texture/Pattern option. - */ - int strengthVersion = setting->getInt("Texture/Strength/StrengthVersion", 1); - if (strengthVersion == 1) { - double legacyStrength = setting->getDouble("Texture/Pattern/Strength", 1.0); - setChecked(true); - setValue(legacyStrength); - } -} - -void KisPressureTextureStrengthOption::writeOptionSetting(KisPropertiesConfigurationSP setting) const -{ - KisCurveOption::writeOptionSetting(setting); - - /** - * Forward compatibility with the Krita < 2.7 - * - * Duplicate the value of the maximum strength into the - * property used by older versions of Krita. - */ - setting->setProperty("Texture/Strength/StrengthVersion", 2); - if (isChecked()) { - setting->setProperty("Texture/Pattern/Strength", value()); - } -} diff --git a/plugins/paintops/libpaintop/kis_pressure_texture_strength_option.h b/plugins/paintops/libpaintop/kis_pressure_texture_strength_option.h index 0f5862bc49..4f7d0b0819 100644 --- a/plugins/paintops/libpaintop/kis_pressure_texture_strength_option.h +++ b/plugins/paintops/libpaintop/kis_pressure_texture_strength_option.h @@ -1,41 +1,38 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_PRESSURE_TEXTURE_STRENGTH_OPTION_H #define __KIS_PRESSURE_TEXTURE_STRENGTH_OPTION_H #include "kis_curve_option.h" #include #include /** * This curve defines how deep the ink (or a pointer) of a brush * penetrates the surface of the canvas, that is how strong we * press on the paper */ class PAINTOP_EXPORT KisPressureTextureStrengthOption : public KisCurveOption { public: KisPressureTextureStrengthOption(); double apply(const KisPaintInformation & info) const; - - void readOptionSetting(const KisPropertiesConfigurationSP setting) override; - void writeOptionSetting(KisPropertiesConfigurationSP setting) const override; }; #endif /* __KIS_PRESSURE_TEXTURE_STRENGTH_OPTION_H */ diff --git a/plugins/paintops/libpaintop/kis_spacing_selection_widget.cpp b/plugins/paintops/libpaintop/kis_spacing_selection_widget.cpp index c95b9855ac..fb423ab29a 100644 --- a/plugins/paintops/libpaintop/kis_spacing_selection_widget.cpp +++ b/plugins/paintops/libpaintop/kis_spacing_selection_widget.cpp @@ -1,130 +1,130 @@ /* * 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_spacing_selection_widget.h" #include #include #include "klocalizedstring.h" #include "kis_signals_blocker.h" #include "kis_slider_spin_box.h" struct KisSpacingSelectionWidget::Private { Private(KisSpacingSelectionWidget *_q) - : q(_q), oldSliderValue(0.1) + : q(_q) { } KisSpacingSelectionWidget *q; - KisDoubleSliderSpinBox *slider; - QCheckBox *autoButton; + KisDoubleSliderSpinBox *slider {0}; + QCheckBox *autoButton {0}; - qreal oldSliderValue; + qreal oldSliderValue {0.1}; void slotSpacingChanged(qreal value); void slotAutoSpacing(bool value); }; KisSpacingSelectionWidget::KisSpacingSelectionWidget(QWidget *parent) : QWidget(parent), m_d(new Private(this)) { m_d->slider = new KisDoubleSliderSpinBox(this); m_d->slider->setRange(0.02, 10.0, 2); m_d->slider->setExponentRatio(3); m_d->slider->setSingleStep(0.01); m_d->slider->setValue(0.1); m_d->slider->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed)); m_d->autoButton = new QCheckBox(this); m_d->autoButton->setText(i18nc("@action:button", "Auto")); m_d->autoButton->setToolTip(i18nc("@info:tooltip", "In auto mode the spacing of the brush will be calculated automatically depending on its size")); m_d->autoButton->setCheckable(true); m_d->autoButton->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed)); QHBoxLayout *layout = new QHBoxLayout(this); layout->addWidget(m_d->autoButton); layout->addWidget(m_d->slider); layout->setMargin(0); connect(m_d->slider, SIGNAL(valueChanged(qreal)), SLOT(slotSpacingChanged(qreal))); connect(m_d->autoButton, SIGNAL(toggled(bool)), SLOT(slotAutoSpacing(bool))); } KisSpacingSelectionWidget::~KisSpacingSelectionWidget() { } qreal KisSpacingSelectionWidget::spacing() const { return autoSpacingActive() ? 0.1 : m_d->slider->value(); } bool KisSpacingSelectionWidget::autoSpacingActive() const { return m_d->autoButton->isChecked(); } qreal KisSpacingSelectionWidget::autoSpacingCoeff() const { return autoSpacingActive() ? m_d->slider->value() : 1.0; } void KisSpacingSelectionWidget::setSpacing(bool isAuto, qreal spacing) { KisSignalsBlocker b1(m_d->autoButton); KisSignalsBlocker b2(m_d->slider); m_d->autoButton->setChecked(isAuto); m_d->slider->setValue(spacing); } void KisSpacingSelectionWidget::Private::slotSpacingChanged(qreal value) { Q_UNUSED(value); emit q->sigSpacingChanged(); } void KisSpacingSelectionWidget::Private::slotAutoSpacing(bool value) { qreal newSliderValue = 0.0; if (value) { newSliderValue = 1.0; oldSliderValue = slider->value(); } else { newSliderValue = oldSliderValue; } { KisSignalsBlocker b(slider); slider->setValue(newSliderValue); } emit q->sigSpacingChanged(); } #include "moc_kis_spacing_selection_widget.moc" diff --git a/plugins/paintops/sketch/kis_sketch_paintop.cpp b/plugins/paintops/sketch/kis_sketch_paintop.cpp index 1671edff80..fb20f0f335 100644 --- a/plugins/paintops/sketch/kis_sketch_paintop.cpp +++ b/plugins/paintops/sketch/kis_sketch_paintop.cpp @@ -1,325 +1,325 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2010 Ricardo Cabello * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * 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_sketch_paintop.h" #include "kis_sketch_paintop_settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_lod_transform.h" #include /* -* Based on Harmony project http://github.com/mrdoob/harmony/ +* Based on Harmony project https://github.com/mrdoob/harmony/ */ // chrome : diff 0.2, sketchy : 0.3, fur: 0.5 // fur : distance / thresholdDistance // shaded: opacity per line :/ // ((1 - (d / 1000)) * 0.1 * BRUSH_PRESSURE), offset == 0 // chrome: color per line :/ //this.context.strokeStyle = "rgba(" + Math.floor(Math.random() * COLOR[0]) + ", " + Math.floor(Math.random() * COLOR[1]) + ", " + Math.floor(Math.random() * COLOR[2]) + ", " + 0.1 * BRUSH_PRESSURE + " )"; // long fur // from: count + offset * -random // to: i point - (offset * -random) + random * 2 // probability distance / thresholdDistnace // shaded: probability : paint always - 0.0 density KisSketchPaintOp::KisSketchPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) { Q_UNUSED(image); Q_UNUSED(node); m_airbrushOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_rateOption.readOptionSetting(settings); m_sketchProperties.readOptionSetting(settings); m_brushOption.readOptionSetting(settings); m_densityOption.readOptionSetting(settings); m_lineWidthOption.readOptionSetting(settings); m_offsetScaleOption.readOptionSetting(settings); m_brush = m_brushOption.brush(); m_dabCache = new KisDabCache(m_brush); m_opacityOption.resetAllSensors(); m_sizeOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_rateOption.resetAllSensors(); m_painter = 0; m_count = 0; } KisSketchPaintOp::~KisSketchPaintOp() { delete m_painter; delete m_dabCache; } void KisSketchPaintOp::drawConnection(const QPointF& start, const QPointF& end, double lineWidth) { if (lineWidth == 1.0) { m_painter->drawThickLine(start, end, lineWidth, lineWidth); } else { m_painter->drawLine(start, end, lineWidth, true); } } void KisSketchPaintOp::updateBrushMask(const KisPaintInformation& info, qreal scale, qreal rotation) { QRect dstRect; m_maskDab = m_dabCache->fetchDab(m_dab->colorSpace(), painter()->paintColor(), info.pos(), KisDabShape(scale, 1.0, rotation), info, 1.0, &dstRect); m_brushBoundingBox = dstRect; m_hotSpot = QPointF(0.5 * m_brushBoundingBox.width(), 0.5 * m_brushBoundingBox.height()); } void KisSketchPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { // Use superclass behavior for lines of zero length. Otherwise, airbrushing can happen faster // than it is supposed to. if (pi1.pos() == pi2.pos()) { KisPaintOp::paintLine(pi1, pi2, currentDistance); } else { doPaintLine(pi1, pi2); } } void KisSketchPaintOp::doPaintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2) { if (!m_brush || !painter()) return; if (!m_dab) { m_dab = source()->createCompositionSourceDevice(); m_painter = new KisPainter(m_dab); m_painter->setPaintColor(painter()->paintColor()); } else { m_dab->clear(); } QPointF prevMouse = pi1.pos(); QPointF mousePosition = pi2.pos(); m_points.append(mousePosition); const qreal lodAdditionalScale = KisLodTransform::lodToScale(painter()->device()); const qreal scale = lodAdditionalScale * m_sizeOption.apply(pi2); if ((scale * m_brush->width()) <= 0.01 || (scale * m_brush->height()) <= 0.01) return; const qreal currentLineWidth = qMax(0.9, lodAdditionalScale * m_lineWidthOption.apply(pi2, m_sketchProperties.lineWidth)); const qreal currentOffsetScale = m_offsetScaleOption.apply(pi2, m_sketchProperties.offset); const double rotation = m_rotationOption.apply(pi2); const double currentProbability = m_densityOption.apply(pi2, m_sketchProperties.probability); // shaded: does not draw this line, chrome does, fur does if (m_sketchProperties.makeConnection) { drawConnection(prevMouse, mousePosition, currentLineWidth); } qreal thresholdDistance = 0.0; // update the mask for simple mode only once // determine the radius if (m_count == 0 && m_sketchProperties.simpleMode) { updateBrushMask(pi2, 1.0, 0.0); //m_radius = qMax(m_maskDab->bounds().width(),m_maskDab->bounds().height()) * 0.5; m_radius = 0.5 * qMax(m_brush->width(), m_brush->height()); } if (!m_sketchProperties.simpleMode) { updateBrushMask(pi2, scale, rotation); m_radius = qMax(m_maskDab->bounds().width(), m_maskDab->bounds().height()) * 0.5; thresholdDistance = pow(m_radius, 2); } if (m_sketchProperties.simpleMode) { // update the radius according scale in simple mode thresholdDistance = pow(m_radius * scale, 2); } // determine density const qreal density = thresholdDistance * currentProbability; // probability behaviour qreal probability = 1.0 - currentProbability; QColor painterColor = painter()->paintColor().toQColor(); QColor randomColor; KoColor color(m_dab->colorSpace()); int w = m_maskDab->bounds().width(); quint8 opacityU8 = 0; quint8 * pixel; qreal distance; QPoint positionInMask; QPointF diff; int size = m_points.size(); // MAIN LOOP for (int i = 0; i < size; i++) { diff = m_points.at(i) - mousePosition; distance = diff.x() * diff.x() + diff.y() * diff.y(); // circle test bool makeConnection = false; if (m_sketchProperties.simpleMode) { if (distance < thresholdDistance) { makeConnection = true; } // mask test } else { if (m_brushBoundingBox.contains(m_points.at(i))) { positionInMask = (diff + m_hotSpot).toPoint(); uint pos = ((positionInMask.y() * w + positionInMask.x()) * m_maskDab->pixelSize()); if (pos < m_maskDab->allocatedPixels() * m_maskDab->pixelSize()) { pixel = m_maskDab->data() + pos; opacityU8 = m_maskDab->colorSpace()->opacityU8(pixel); if (opacityU8 != 0) { makeConnection = true; } } } } if (!makeConnection) { // check next point continue; } if (m_sketchProperties.distanceDensity) { probability = distance / density; } KisRandomSourceSP randomSource = pi2.randomSource(); // density check if (randomSource->generateNormalized() >= probability) { QPointF offsetPt = diff * currentOffsetScale; if (m_sketchProperties.randomRGB) { /** * Since the order of calculation of function * parameters is not defined by C++ standard, we * should generate values in an external code snippet * which has a definite order of execution. */ qreal r1 = randomSource->generateNormalized(); qreal r2 = randomSource->generateNormalized(); qreal r3 = randomSource->generateNormalized(); // some color transformation per line goes here randomColor.setRgbF(r1 * painterColor.redF(), r2 * painterColor.greenF(), r3 * painterColor.blueF()); color.fromQColor(randomColor); m_painter->setPaintColor(color); } // distance based opacity quint8 opacity = OPACITY_OPAQUE_U8; if (m_sketchProperties.distanceOpacity) { opacity *= qRound((1.0 - (distance / thresholdDistance))); } if (m_sketchProperties.randomOpacity) { opacity *= randomSource->generateNormalized(); } m_painter->setOpacity(opacity); if (m_sketchProperties.magnetify) { drawConnection(mousePosition + offsetPt, m_points.at(i) - offsetPt, currentLineWidth); } else { drawConnection(mousePosition + offsetPt, mousePosition - offsetPt, currentLineWidth); } } }// end of MAIN LOOP m_count++; QRect rc = m_dab->extent(); quint8 origOpacity = m_opacityOption.apply(painter(), pi2); painter()->bitBlt(rc.x(), rc.y(), m_dab, rc.x(), rc.y(), rc.width(), rc.height()); painter()->renderMirrorMask(rc, m_dab); painter()->setOpacity(origOpacity); } KisSpacingInformation KisSketchPaintOp::paintAt(const KisPaintInformation& info) { doPaintLine(info, info); return updateSpacingImpl(info); } KisSpacingInformation KisSketchPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { return KisPaintOpPluginUtils::effectiveSpacing(0.0, 0.0, true, 0.0, false, 0.0, false, 0.0, KisLodTransform::lodToScale(painter()->device()), &m_airbrushOption, nullptr, info); } KisTimingInformation KisSketchPaintOp::updateTimingImpl(const KisPaintInformation &info) const { return KisPaintOpPluginUtils::effectiveTiming(&m_airbrushOption, &m_rateOption, info); } diff --git a/plugins/paintops/tangentnormal/kis_tangent_tilt_option.cpp b/plugins/paintops/tangentnormal/kis_tangent_tilt_option.cpp index 4e8f3ebbfb..7390fc598d 100644 --- a/plugins/paintops/tangentnormal/kis_tangent_tilt_option.cpp +++ b/plugins/paintops/tangentnormal/kis_tangent_tilt_option.cpp @@ -1,258 +1,258 @@ /* This file is part of the KDE project * * Copyright (C) 2015 Wolthera van Hövell tot Westerflier * * 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_tangent_tilt_option.h" #include #include #include #include "ui_wdgtangenttiltoption.h" #include "kis_global.h" class KisTangentTiltOptionWidget: public QWidget, public Ui::WdgTangentTiltOptions { public: KisTangentTiltOptionWidget(QWidget *parent = 0) : QWidget(parent) { setupUi(this); } }; KisTangentTiltOption::KisTangentTiltOption() : KisPaintOpOption(KisPaintOpOption::GENERAL, false) { m_checkable = false; m_options = new KisTangentTiltOptionWidget(); //Setup tangent tilt. m_options->comboRed->setCurrentIndex(0); m_options->comboGreen->setCurrentIndex(2); m_options->comboBlue->setCurrentIndex(4); m_options->sliderElevationSensitivity->setRange(0, 100, 0); m_options->sliderElevationSensitivity->setValue(100); m_options->sliderElevationSensitivity->setSuffix(i18n("%")); m_options->sliderMixValue->setRange(0, 100, 0); m_options->sliderMixValue->setValue(50); m_options->sliderMixValue->setSuffix(i18n("%")); connect(m_options->comboRed, SIGNAL(currentIndexChanged(int)), SLOT(emitSettingChanged())); connect(m_options->comboGreen, SIGNAL(currentIndexChanged(int)), SLOT(emitSettingChanged())); connect(m_options->comboBlue, SIGNAL(currentIndexChanged(int)), SLOT(emitSettingChanged())); connect(m_options->comboRed, SIGNAL(currentIndexChanged(int)), m_options->TangentTiltPreview, SLOT(setRedChannel(int))); connect(m_options->comboGreen, SIGNAL(currentIndexChanged(int)), m_options->TangentTiltPreview, SLOT(setGreenChannel(int))); connect(m_options->comboBlue, SIGNAL(currentIndexChanged(int)), m_options->TangentTiltPreview, SLOT(setBlueChannel(int))); connect(m_options->optionTilt, SIGNAL(toggled(bool)), SLOT(emitSettingChanged())); connect(m_options->optionDirection, SIGNAL(toggled(bool)), SLOT(emitSettingChanged())); connect(m_options->optionRotation, SIGNAL(toggled(bool)), SLOT(emitSettingChanged())); connect(m_options->optionMix, SIGNAL(toggled(bool)), SLOT(emitSettingChanged())); connect(m_options->sliderElevationSensitivity, SIGNAL(valueChanged(qreal)), SLOT(emitSettingChanged())); connect(m_options->sliderMixValue, SIGNAL(valueChanged(qreal)), SLOT(emitSettingChanged())); m_options->sliderMixValue->setVisible(false); setConfigurationPage(m_options); } KisTangentTiltOption::~KisTangentTiltOption() { delete m_options; } //options int KisTangentTiltOption::redChannel() const { return m_options->comboRed->currentIndex(); } int KisTangentTiltOption::greenChannel() const { return m_options->comboGreen->currentIndex(); } int KisTangentTiltOption::blueChannel() const { return m_options->comboBlue->currentIndex(); } int KisTangentTiltOption::directionType() const { int type=0; if (m_options->optionTilt->isChecked()==true) { type=0; } else if (m_options->optionDirection->isChecked()==true) { type=1; } else if (m_options->optionRotation->isChecked()==true) { type=2; } else if (m_options->optionMix->isChecked()==true) { type=3; } else { warnKrita<<"There's something odd with the radio buttons. We'll use Tilt"; } return type; } double KisTangentTiltOption::elevationSensitivity() const { return m_options->sliderElevationSensitivity->value(); } double KisTangentTiltOption::mixValue() const { return m_options->sliderMixValue->value(); } void KisTangentTiltOption::swizzleAssign(qreal const horizontal, qreal const vertical, qreal const depth, qreal *component, int index, qreal maxvalue) { switch(index) { case 0: *component = horizontal; break; case 1: *component = maxvalue-horizontal; break; case 2: *component = vertical; break; case 3: *component = maxvalue-vertical; break; case 4: *component = depth; break; case 5: *component = maxvalue-depth; break; } } void KisTangentTiltOption::apply(const KisPaintInformation& info,qreal *r,qreal *g,qreal *b) { - //formula based on http://www.cerebralmeltdown.com/programming_projects/Altitude%20and%20Azimuth%20to%20Vector/index.html + //formula based on https://www.cerebralmeltdown.com/programming_projects/Altitude%20and%20Azimuth%20to%20Vector/index.html /* It doesn't make sense of have higher than 8bit color depth. * Instead we make sure in the paintAt function of kis_tangent_normal_paintop to pick an 8bit space of the current * color space if the space is an RGB space. If not, it'll pick sRGB 8bit. */ qreal halfvalue = 0.5; qreal maxvalue = 1.0; //have the azimuth and altitude in degrees. qreal direction = KisPaintInformation::tiltDirection(info, true)*360.0; qreal elevation= (info.tiltElevation(info, 60.0, 60.0, true)*90.0); if (directionType() == 0) { direction = KisPaintInformation::tiltDirection(info, true)*360.0; elevation= (info.tiltElevation(info, 60.0, 60.0, true)*90.0); } else if (directionType() == 1) { direction = (0.75 + info.drawingAngle() / (2.0 * M_PI))*360.0; elevation= 0;//turns out that tablets that don't support tilt just return 90 degrees for elevation. } else if (directionType() == 2) { direction = info.rotation(); elevation= (info.tiltElevation(info, 60.0, 60.0, true)*90.0);//artpens have tilt-recognition, so this should work. } else if (directionType() == 3) {//mix of tilt+direction qreal mixamount = mixValue()/100.0; direction = (KisPaintInformation::tiltDirection(info, true)*360.0*(1.0-mixamount))+((0.75 + info.drawingAngle() / (2.0 * M_PI))*360.0*(mixamount)); elevation= (info.tiltElevation(info, 60.0, 60.0, true)*90.0); } //subtract/add the rotation of the canvas. if (directionType() != 1) { direction = normalizeAngleDegrees(direction - info.canvasRotation()); } //limit the direction/elevation //qreal elevationMax = (elevationSensitivity()*90.0)/100.0; qreal elevationT = elevation*(elevationSensitivity()/100.0)+(90-(elevationSensitivity()*90.0)/100.0); elevation = static_cast(elevationT); //convert to radians. // Convert this to kis_global's radian function. direction = kisDegreesToRadians(direction); elevation = kisDegreesToRadians(elevation); //make variables for axes for easy switching later on. qreal horizontal, vertical, depth; //spherical coordinates always center themselves around the origin, leading to values. We need to work around those... horizontal = cos(elevation)*sin(direction); if (horizontal>0.0) { horizontal= halfvalue+(fabs(horizontal)*halfvalue); } else { horizontal= halfvalue-(fabs(horizontal)*halfvalue); } vertical = cos(elevation)*cos(direction); if (vertical>0.0) { vertical = halfvalue+(fabs(vertical)*halfvalue); } else { vertical = halfvalue-(fabs(vertical)*halfvalue); } if (info.canvasMirroredH()) {horizontal = maxvalue - horizontal;} if (info.canvasMirroredV()) {vertical = maxvalue - vertical;} depth = sin(elevation)*maxvalue; //assign right components to correct axes. swizzleAssign(horizontal, vertical, depth, r, redChannel(), maxvalue); swizzleAssign(horizontal, vertical, depth, g, greenChannel(), maxvalue); swizzleAssign(horizontal, vertical, depth, b, blueChannel(), maxvalue); } /*settings*/ void KisTangentTiltOption::writeOptionSetting(KisPropertiesConfigurationSP setting) const { setting->setProperty(TANGENT_RED, redChannel()); setting->setProperty(TANGENT_GREEN, greenChannel()); setting->setProperty(TANGENT_BLUE, blueChannel()); setting->setProperty(TANGENT_TYPE, directionType()); setting->setProperty(TANGENT_EV_SEN, elevationSensitivity()); setting->setProperty(TANGENT_MIX_VAL, mixValue()); } void KisTangentTiltOption::readOptionSetting(const KisPropertiesConfigurationSP setting) { m_options->comboRed->setCurrentIndex(setting->getInt(TANGENT_RED, 0)); m_options->comboGreen->setCurrentIndex(setting->getInt(TANGENT_GREEN, 2)); m_options->comboBlue->setCurrentIndex(setting->getInt(TANGENT_BLUE, 4)); //The comboboxes are connected to the TangentTiltPreview, so that gets automatically updated by them. if (setting->getInt(TANGENT_TYPE)== 0){ m_options->optionTilt->setChecked(true); m_options->sliderMixValue->setVisible(false); } else if (setting->getInt(TANGENT_TYPE)== 1) { m_options->optionDirection->setChecked(true); m_options->sliderMixValue->setVisible(false); } else if (setting->getInt(TANGENT_TYPE)== 2) { m_options->optionRotation->setChecked(true); m_options->sliderMixValue->setVisible(false); } else if (setting->getInt(TANGENT_TYPE)== 3) { m_options->optionMix->setChecked(true); m_options->sliderMixValue->setVisible(true); } m_options->sliderElevationSensitivity->setValue(setting->getDouble(TANGENT_EV_SEN, 100)); m_options->sliderMixValue->setValue(setting->getDouble(TANGENT_MIX_VAL, 50)); } diff --git a/plugins/python/CMakeLists.txt b/plugins/python/CMakeLists.txt index d82c284d08..3e46581bfd 100644 --- a/plugins/python/CMakeLists.txt +++ b/plugins/python/CMakeLists.txt @@ -1,117 +1,118 @@ # 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(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) # 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 544eda162d..fb4a784a97 100644 --- a/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop +++ b/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop @@ -1,57 +1,59 @@ [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[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/colorspace/kritapykrita_colorspace.desktop b/plugins/python/colorspace/kritapykrita_colorspace.desktop index 11c1c3415c..229e5afa66 100644 --- a/plugins/python/colorspace/kritapykrita_colorspace.desktop +++ b/plugins/python/colorspace/kritapykrita_colorspace.desktop @@ -1,59 +1,61 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=colorspace X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Color Space Name[ar]=الفضاء اللوني Name[ca]=Espai de color Name[ca@valencia]=Espai de color Name[cs]=Barevný prostor Name[de]=Farbraum Name[el]=Χρωματικός χώρος Name[en_GB]=Colour Space Name[es]=Espacio de color +Name[et]=Värviruum Name[eu]=Kolore-espazioa Name[fi]=Väriavaruus Name[fr]=Espace colorimétrique Name[gl]=Espazo de cores Name[is]=Litrýmd Name[it]=Spazio dei colori Name[ko]=색상 공간 Name[nl]=Kleurruimte Name[nn]=Fargerom Name[pl]=Przestrzeń barw Name[pt]=Espaço de Cores Name[pt_BR]=Espaço de cores Name[sk]=Farebný priestor Name[sv]=Färgrymd Name[tr]=Renk Aralığı Name[uk]=Простір кольорів Name[x-test]=xxColor Spacexx Name[zh_CN]=色彩空间 Name[zh_TW]=色彩空間 Comment=Plugin to change color space to selected documents Comment[ar]=ملحقة لتغيير الفضاء اللوني في المستندات المحددة Comment[ca]=Un connector per a canviar l'espai de color dels documents seleccionats Comment[ca@valencia]=Un connector per a canviar l'espai de color dels documents seleccionats Comment[cs]=Modul pro změnu rozsahu barvy pro vybrané dokumenty Comment[el]=Πρόσθετο αλλαγής χρωματικού χώρου σε επιλεγμένα έγγραφα Comment[en_GB]=Plugin to change colour space to selected documents Comment[es]=Complemento para cambiar el espacio de color de los documentos seleccionados +Comment[et]=Plugin valitud dokumentide värviruumi muutmiseks Comment[eu]=Hautatutako dokumentuei kolore-espazioa aldatzeko plugina Comment[fi]=Liitännäinen valittujen tiedostojen väriavaruuden muuttamiseksi Comment[fr]=Module externe pour l'espace de couleurs des documents sélectionnés Comment[gl]=Complemento para cambiar o espazo de cores dos documentos seleccionados. Comment[it]=Estensione per cambiare lo spazio dei colori ai documenti selezionati Comment[ko]=선택한 문서로 색상 공간을 변경하는 플러그인 Comment[nl]=Plug-in om kleurruimte in geselecteerde documenten te wijzigen Comment[nn]=Programtillegg for å byta fargerom på utvalde dokument Comment[pl]=Wtyczka do zmiany przestrzeni barw wybranych dokumentów Comment[pt]='Plugin' para mudar o espaço de cores do documento seleccionado Comment[pt_BR]=Plugin para alterar o espaço de cores em documentos selecionados Comment[sv]=Insticksprogram för att ändra färgrymd för valda dokument Comment[tr]=Seçili belgede renk aralığını değiştirmek için eklenti Comment[uk]=Додаток для зміни простору кольорів у позначених документах Comment[x-test]=xxPlugin to change color space to selected documentsxx Comment[zh_CN]=用于更改选定文档色彩空间的插件 Comment[zh_TW]=用於變更色彩空間為選定檔案的外掛程式 diff --git a/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop b/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop index f72ea33d5a..4184aee956 100644 --- a/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop +++ b/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop @@ -1,58 +1,60 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=comics_project_management_tools X-Krita-Manual=README.html X-Python-2-Compatible=false Name=Comics Project Management Tools Name[ar]=أدوات إدارة المشاريع الهزليّة Name[ca]=Eines per a la gestió dels projectes de còmics Name[ca@valencia]=Eines per a la gestió dels projectes de còmics Name[cs]=Nástroje pro správu projektů komixů Name[el]=Εργαλεία διαχείρισης έργων ιστοριών σε εικόνες Name[en_GB]=Comics Project Management Tools Name[es]=Herramientas de gestión de proyectos de cómics +Name[et]=Koomiksite projektihalduse tööriistad Name[eu]=Komikien proiektuak kudeatzeko tresnak Name[fi]=Sarjakuvaprojektien hallintatyökalut Name[fr]=Outils de gestion d'un projet de bande dessinée Name[gl]=Ferramentas de xestión de proxectos de cómics Name[is]=Verkefnisstjórn teiknimyndasögu Name[it]=Strumenti per la gestione dei progetti di fumetti Name[ko]=만화 프로젝트 관리 도구 Name[nl]=Hulpmiddelen voor projectbeheer van strips Name[nn]=Prosjekthandsaming for teikneseriar Name[pl]=Narzędzia do zarządzania projektami komiksów Name[pt]=Ferramentas de Gestão de Projectos de Banda Desenhada Name[pt_BR]=Ferramentas de gerenciamento de projeto de quadrinhos Name[sv]=Projekthanteringsverktyg för tecknade serier Name[tr]=Çizgi Roman Projesi Yönetimi Araçları Name[uk]=Інструменти для керування проєктами коміксів Name[x-test]=xxComics Project Management Toolsxx Name[zh_CN]=漫画项目管理工具 Name[zh_TW]=漫畫專案管理工具 Comment=Tools for managing comics. Comment[ar]=أدوات لإدارة الهزليّات. Comment[ca]=Eines per a gestionar els còmics. Comment[ca@valencia]=Eines per a gestionar els còmics. Comment[cs]=Nástroje pro správu komixů. Comment[el]=Εργαλεία για τη διαχείριση ιστοριών σε εικόνες. Comment[en_GB]=Tools for managing comics. Comment[es]=Herramientas para gestionar cómics. +Comment[et]=Koomiksite haldamise tööriistad. Comment[eu]=Komikiak kudeatzeko tresnak. Comment[fi]=Sarjakuvien hallintatyökalut. Comment[fr]=Outils pour gérer les bandes dessinées. Comment[gl]=Ferramentas para xestionar cómics. Comment[is]=Verkfæri til að stýra gerð teiknimyndasögu. Comment[it]=Strumenti per la gestione dei fumetti. Comment[ko]=만화 관리 도구입니다. Comment[nl]=Hulpmiddelen voor beheer van strips. Comment[nn]=Verktøy for handsaming av teikneseriar Comment[pl]=Narzędzie do zarządzania komiksami. Comment[pt]=Ferramentas para gerir bandas desenhadas. Comment[pt_BR]=Ferramentas para gerenciar quadrinhos. Comment[sv]=Verktyg för att hantera tecknade serier. Comment[tr]=Çizgi romanları yönetmek için araçlar. Comment[uk]=Інструменти для керування коміксами Comment[x-test]=xxTools for managing comics.xx Comment[zh_CN]=用于管理漫画的工具。 Comment[zh_TW]=管理漫畫的工具。 diff --git a/plugins/python/documenttools/kritapykrita_documenttools.desktop b/plugins/python/documenttools/kritapykrita_documenttools.desktop index c34c388ee2..650d03a0f3 100644 --- a/plugins/python/documenttools/kritapykrita_documenttools.desktop +++ b/plugins/python/documenttools/kritapykrita_documenttools.desktop @@ -1,56 +1,58 @@ [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[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[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 2469aeb41d..63e1511745 100644 --- a/plugins/python/exportlayers/kritapykrita_exportlayers.desktop +++ b/plugins/python/exportlayers/kritapykrita_exportlayers.desktop @@ -1,59 +1,61 @@ [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[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[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 41a19bb39f..3be984b34b 100644 --- a/plugins/python/filtermanager/kritapykrita_filtermanager.desktop +++ b/plugins/python/filtermanager/kritapykrita_filtermanager.desktop @@ -1,58 +1,60 @@ [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[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/hello/kritapykrita_hello.desktop b/plugins/python/hello/kritapykrita_hello.desktop index 1ec86ba1df..c4a0c7ceae 100644 --- a/plugins/python/hello/kritapykrita_hello.desktop +++ b/plugins/python/hello/kritapykrita_hello.desktop @@ -1,61 +1,63 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=hello X-Krita-Manual=Manual.html X-Python-2-Compatible=true Name=Hello World Name[ar]=مرحبا يا عالم Name[ca]=Hola món Name[ca@valencia]=Hola món Name[cs]=Hello World Name[de]=Hallo Welt Name[el]=Hello World Name[en_GB]=Hello World Name[es]=Hola mundo +Name[et]=Tere, maailm Name[eu]=Kaixo mundua Name[fi]=Hei maailma Name[fr]=Bonjour tout le monde Name[gl]=Ola mundo Name[is]=Halló Heimur Name[it]=Ciao mondo Name[ko]=전 세계 여러분 안녕하세요 Name[nl]=Hallo wereld Name[nn]=Hei, verda Name[pl]=Witaj świecie Name[pt]=Olá Mundo Name[pt_BR]=Olá mundo Name[sk]=Ahoj svet Name[sv]=Hello World Name[tg]=Салом ҷаҳон Name[tr]=Merhaba Dünya Name[uk]=Привіт, світе Name[x-test]=xxHello Worldxx Name[zh_CN]=Hello World Name[zh_TW]=你好,世界 Comment=Basic plugin to test PyKrita Comment[ar]=ملحقة أساسية لاختبار PyKrita Comment[ca]=Connector bàsic per a provar el PyKrita Comment[ca@valencia]=Connector bàsic per a provar el PyKrita Comment[cs]=Základní modul pro testování PyKrita Comment[de]=Basismodul zum Testen von PyKrita Comment[el]=Βασικό πρόσθετο δοκιμής PyKrita Comment[en_GB]=Basic plugin to test PyKrita Comment[es]=Complemento básico para probar PyKrita +Comment[et]=Baasplugin PyKrita testimiseks Comment[eu]=PyKrita probatzeko plugina Comment[fi]=Perusliitännäinen PyKritan kokeilemiseksi Comment[fr]=Module externe élémentaire pour tester PyKrita Comment[gl]=Complemento básico para probar PyKrita. Comment[it]=Estensione di base per provare PyKrita Comment[ko]=PyKrita 테스트용 기본 플러그인 Comment[nl]=Basisplug-in om PyKrita te testen Comment[nn]=Enkelt programtillegg for testing av PyKrita Comment[pl]=Podstawowa wtyczka do wypróbowania PyKrity Comment[pt]='Plugin' básico para testar o PyKrita Comment[pt_BR]=Plugin básico para testar o PyKrita Comment[sv]=Enkelt insticksprogram för att utprova PyKrita Comment[tr]=PyKrita'yı test etmek için temel eklenti Comment[uk]=Базовий додаток для тестування PyKrita Comment[x-test]=xxBasic plugin to test PyKritaxx Comment[zh_CN]=用于测试 PyKrita 的简易插件 Comment[zh_TW]=測試 PyKrita 的基本外掛程式 diff --git a/plugins/python/highpass/kritapykrita_highpass.desktop b/plugins/python/highpass/kritapykrita_highpass.desktop index e40f7724c3..bb6471a1cc 100644 --- a/plugins/python/highpass/kritapykrita_highpass.desktop +++ b/plugins/python/highpass/kritapykrita_highpass.desktop @@ -1,53 +1,55 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=highpass X-Python-2-Compatible=false Name=Highpass Filter Name[ca]=Filtre passaalt Name[ca@valencia]=Filtre passaalt Name[cs]=Filtr s horní propustí Name[de]=Hochpassfilter Name[el]=Υψιπερατό φίλτρο Name[en_GB]=Highpass Filter Name[es]=Filtro paso alto +Name[et]=Kõrgpääsfilter Name[eu]=Goi-igaropeneko iragazkia Name[fr]=Filtre passe-haut Name[gl]=Filtro de paso alto Name[it]=Filtro di accentuazione passaggio Name[ko]=하이패스 필터 Name[nl]=Hoogdoorlaatfilter Name[nn]=Høgpass-filter Name[pl]=Filtr górnoprzepustowy Name[pt]=Filtro Passa-Alto Name[pt_BR]=Filtro passa alta Name[sv]=Högpassfilter Name[tr]=Yüksek Geçirgen Süzgeç Name[uk]=Високочастотний фільтр Name[x-test]=xxHighpass Filterxx Name[zh_CN]=高通滤镜 Name[zh_TW]=高通濾鏡 Comment=Highpass Filter, based on http://registry.gimp.org/node/7385 Comment[ca]=Filtre passaalt, basat en el http://registry.gimp.org/node/7385 Comment[ca@valencia]=Filtre passaalt, basat en el http://registry.gimp.org/node/7385 Comment[cs]=Filtr s horní propustí založený na http://registry.gimp.org/node/7385 Comment[de]=Hochpassfilter, Grundlage ist http://registry.gimp.org/node/7385 Comment[el]=Υψιπερατό φίλτρο, με βάση το http://registry.gimp.org/node/7385 Comment[en_GB]=Highpass Filter, based on http://registry.gimp.org/node/7385 Comment[es]=Filtro paso alto, basado en http://registry.gimp.org/node/7385 +Comment[et]=Kõrgpääsfilter, mille aluseks on http://registry.gimp.org/node/7385 Comment[eu]=Goi-igaropeneko iragazkia, honetan oinarritua http://registry.gimp.org/node/7385 Comment[fr]=Filtre passe-haut, fondé sur http://registry.gimp.org/node/7385 Comment[gl]=Filtro de paso alto, baseado en http://registry.gimp.org/node/7385. Comment[it]=Filtro di accentuazione passaggio, basato su http://registry.gimp.org/node/7385 Comment[ko]=http://registry.gimp.org/node/7385에 기반한 하이패스 필터 Comment[nl]=Hoogdoorlaatfilter, gebaseerd op http://registry.gimp.org/node/7385 Comment[nn]=Høgpass-filter, basert på http://registry.gimp.org/node/7385 Comment[pl]=Filtr górnoprzepustowy, oparty na http://registry.gimp.org/node/7385 Comment[pt]=Filtro passa-alto, baseado em http://registry.gimp.org/node/7385 Comment[pt_BR]=Filtro passa alta, baseado em http://registry.gimp.org/node/7385 Comment[sv]=Högpassfilter, baserat på http://registry.gimp.org/node/7385 Comment[tr]=http://registry.gimp.org/node/7385 tabanlı Yüksek Geçirgen Süzgeç Comment[uk]=Високочастотний фільтр, засновано на on http://registry.gimp.org/node/7385 Comment[x-test]=xxHighpass Filter, based on http://registry.gimp.org/node/7385xx Comment[zh_CN]=高通滤镜,基于 http://registry.gimp.org/node/7385 Comment[zh_TW]=高通濾鏡,基於 http://registry.gimp.org/node/7385 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 6a13606e33..c67432b5d7 100644 --- a/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop +++ b/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop @@ -1,45 +1,49 @@ [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[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[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/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop b/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop index 750d85fd0b..d2d146060a 100644 --- a/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop +++ b/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop @@ -1,52 +1,56 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=lastdocumentsdocker X-Python-2-Compatible=false X-Krita-Manual=Manual.html Name=Last Documents Docker Name[ar]=رصيف بآخر المستندات Name[ca]=Acoblador Darrers documents Name[ca@valencia]=Acoblador Darrers documents Name[el]=Προσάρτηση τελευταίων εγγράφοων Name[en_GB]=Last Documents Docker Name[es]=Panel de últimos documentos +Name[et]=Viimaste dokumentide dokk Name[eu]=Azken dokumentuen panela Name[fi]=Viimeisimpien tiedostojen telakka Name[fr]=Récemment ouverts Name[gl]=Doca dos últimos documentos Name[it]=Area di aggancio Ultimi documenti Name[ko]=마지막 문서 도킹 패널 Name[nl]=Laatste documenten verankering Name[nn]=Dokk for nyleg opna dokument Name[pl]=Dok ostatnich dokumentów Name[pt]=Área dos Últimos Documentos +Name[pt_BR]=Área dos últimos documentos Name[sv]=Dockningsfönster för senaste dokument Name[tr]=Son Belgeler Doku Name[uk]=Бічна панель останніх документів Name[x-test]=xxLast Documents Dockerxx Name[zh_CN]=最近文档工具面板 Name[zh_TW]=「最後檔案」面板 Comment=A Python-based docker for show thumbnails to last ten documents Comment[ar]=رصيف بِ‍«پيثون» لعرض مصغّرات آخر ١٠ مستندات مفتوحة Comment[ca]=Un acoblador basat en Python per a mostrar miniatures dels darrers deu documents Comment[ca@valencia]=Un acoblador basat en Python per a mostrar miniatures dels darrers deu documents Comment[el]=Ένα εργαλείο προσάρτησης σε Python για την εμφάνιση επισκοπήσεων των δέκα τελευταίων εγγράφων Comment[en_GB]=A Python-based docker for show thumbnails to last ten documents Comment[es]=Un panel basado en Python para mostrar miniaturas de los últimos diez documentos +Comment[et]=Pythoni-põhine dokk viimase kümne dokumendi pisipildi näitamiseks Comment[eu]=Azken hamar dokumentuen koadro-txikiak erakusteko Python-oinarridun panel bat Comment[fi]=Python-pohjainen telakka viimeisimpien kymmenen tiedoston pienoiskuvien näyttämiseen Comment[fr]=Panneau en Python pour afficher les vignettes des dix derniers documents Comment[gl]=Unha doca baseada en Python para mostrar as miniaturas dos últimos dez documentos. Comment[it]=Un'area di aggancio basata su Python per mostrare miniature degli ultimi dieci documenti. Comment[ko]=10개의 문서를 표시할 수 있는 Python 기반 도킹 패널 Comment[nl]=Een op Python gebaseerde vastzetter om miniaturen te tonen naar de laatste tien documenten. Comment[nn]=Python-basert dokk for vising av miniatyrbilete av dei siste ti dokumenta Comment[pl]=Dok oparty na pythonie do wyświetlania miniatur ostatnich dziesięciu dokumentów Comment[pt]=Uma área acoplável, feita em Python, para mostrar as miniaturas dos últimos dez documentos +Comment[pt_BR]=Uma área acoplável feita em Python para mostrar as miniaturas dos últimos dez documentos Comment[sv]=Ett Python-baserat dockningsfönster för att visa miniatyrbilder för de tio senaste dokumenten Comment[tr]=Son on belgenin küçük resmini göstermek için Python-tabanlı bir dok Comment[uk]=Бічна панель на основі Python для показу мініатюр останніх десяти документів Comment[x-test]=xxA Python-based docker for show thumbnails to last ten documentsxx Comment[zh_CN]=这是一个基于 Python 的工具面板插件,它可以显示最近十个文档的缩略图 Comment[zh_TW]=基於 Python 的面板,用於顯示最後 10 個檔案縮圖 diff --git a/plugins/python/mixer_slider_docker/Manual.html b/plugins/python/mixer_slider_docker/Manual.html new file mode 100644 index 0000000000..52215d9ef1 --- /dev/null +++ b/plugins/python/mixer_slider_docker/Manual.html @@ -0,0 +1,22 @@ + + + + +Krita-docker-color-slider Plugin Manual + + + +

    Mixer Slider Docker

    +

    A docker which allows you to choose from the gradient between two colors.

    +

    Basic Usage

    +

    Go to Settings -> Configure Krita -> Python Plugin Manager. + Enable "Mixer Slider docker."

    +

    Right-click on menu bar, and enable "Mixer Slider Docker." A docker will appear in the window. +The button on the left with the text "S" is for settings. Click on it to set the number of slider lines.

    +

    The right part of the docker contains the sliders. Each line has a left color, a slider bar, and a right color.

    +

    Click the left/right color to set it to the current foreground color. Click and move on the slider to set + the current foreground color to the color your mouse is hovering. An indicator will appear to show the color +you just selected.

    + + + diff --git a/plugins/python/mixer_slider_docker/__init__.py b/plugins/python/mixer_slider_docker/__init__.py new file mode 100644 index 0000000000..f78612fc8e --- /dev/null +++ b/plugins/python/mixer_slider_docker/__init__.py @@ -0,0 +1,19 @@ +''' + Copyright (C) 2019 Tusooa Zhu + + This file is part of Krita-docker-color-slider. + + Krita-docker-color-slider is free software: you can 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. + + Krita-docker-color-slider is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Krita-docker-color-slider. If not, see . +''' +from .mixer_slider_docker import * diff --git a/plugins/python/mixer_slider_docker/color_slider.py b/plugins/python/mixer_slider_docker/color_slider.py new file mode 100644 index 0000000000..d728b0f8f0 --- /dev/null +++ b/plugins/python/mixer_slider_docker/color_slider.py @@ -0,0 +1,138 @@ +''' + Copyright (C) 2019 Tusooa Zhu + + This file is part of Krita-docker-color-slider. + + Krita-docker-color-slider is free software: you can 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. + + Krita-docker-color-slider is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Krita-docker-color-slider. If not, see . +''' +from PyQt5.QtWidgets import QWidget +from PyQt5.QtGui import QPixmap, QPainter, QColor, QBrush, QPolygon +from PyQt5.QtCore import QPoint +from krita import ManagedColor + + +class ColorSlider(QWidget): + default_color = ManagedColor("", "", "") + + def __init__(self, docker, left_color=default_color, right_color=default_color, parent=None): + super(ColorSlider, self).__init__(parent) + self.docker = docker + self.left_color = left_color + self.right_color = right_color + self.slider_pixmap = None + self.value_x = None + self.cursor_fill_color = QColor.fromRgbF(1, 1, 1, 1) + self.cursor_outline_color = QColor.fromRgbF(0, 0, 0, 1) + self.need_redraw = True + + def set_color(self, pos, color): + if pos == 'left': + if self.left_color is not color: + self.left_color = color + self.need_redraw = True + else: + if self.right_color is not color: + self.right_color = color + self.need_redraw = True + + def update_slider(self): + ''' + Update the slider to a gradient between the two colors. + + The painting of the slider comes from the program Krita. The original code can be accessed + at the following URL. + https://github.com/KDE/krita/blob/master/plugins/dockers/advancedcolorselector/kis_shade_selector_line.cpp + ''' + if self.need_redraw: + patch_count = self.width() + base_hsva = list(self.docker.managedcolor_to_qcolor(self.left_color).getHsvF()) + dest_hsva = list(self.docker.managedcolor_to_qcolor(self.right_color).getHsvF()) + diff_hsva = [(dest_hsva[i] - base_hsva[i]) for i in range(4)] + if dest_hsva[0] == -1.0: + diff_hsva[0] = 0 + elif base_hsva[0] == -1.0: + diff_hsva[0] = 0 + base_hsva[0] = dest_hsva[0] + elif diff_hsva[0] > 0.5: # make sure the sliding goes through a minor arc + diff_hsva[0] = diff_hsva[0] - 1.0 + elif diff_hsva[0] < -0.5: + diff_hsva[0] = diff_hsva[0] + 1.0 + + step_hsva = [x / patch_count for x in diff_hsva] + + self.slider_pixmap = QPixmap(self.width(), self.height()) + painter = QPainter(self.slider_pixmap) + + for i in range(patch_count): + hue = base_hsva[0] + i * step_hsva[0] + while hue < 0.0: + hue += 1.0 + + while hue > 1.0: + hue -= 1.0 + + saturation = base_hsva[1] + i * step_hsva[1] + value = base_hsva[2] + i * step_hsva[2] + cur_color = QColor.fromHsvF(hue, saturation, value) + painter.fillRect(i, 0, 1, self.height(), cur_color) + + painter.end() + + self.need_redraw = False + + widget_painter = QPainter(self) + self.rendered_image = self.slider_pixmap.toImage() + + widget_painter.drawImage(0, 0, self.rendered_image) + if self.value_x is not None: + start_x = self.value_x + start_y = self.height() / 2 + delta_x = self.height() / 3 + delta_y = self.height() / 3 + points = [QPoint(start_x, start_y), + QPoint(start_x - delta_x, start_y + delta_y), + QPoint(start_x + delta_x, start_y + delta_y)] + widget_painter.setBrush(QBrush(self.cursor_fill_color)) + widget_painter.setPen(self.cursor_outline_color) + widget_painter.drawPolygon(QPolygon(points)) + + def paintEvent(self, event): + self.update_slider() + + def resizeEvent(self, event): # after resizing the widget, force-redraw the underlying slider + self.need_redraw = True + + def adjust_pos_x(self, x): # adjust the x to make it in the range of [0, width - 1] + if x < 0: + return 0 + if x >= self.width(): + return self.width() - 1 + return x + + def mouseMoveEvent(self, event): + pos = event.pos() + self.value_x = self.adjust_pos_x(pos.x()) + self.update() + + def mouseReleaseEvent(self, event): + pos = event.pos() + self.value_x = self.adjust_pos_x(pos.x()) + y = int(self.height() / 2) + fixed_pos = QPoint(self.value_x, y) + color = self.rendered_image.pixelColor(fixed_pos) + mc = self.docker.qcolor_to_managedcolor(color) + if self.docker.canvas() is not None: + if self.docker.canvas().view() is not None: + self.docker.canvas().view().setForeGroundColor(mc) + self.update() diff --git a/plugins/python/mixer_slider_docker/kritapykrita_mixer_slider_docker.desktop b/plugins/python/mixer_slider_docker/kritapykrita_mixer_slider_docker.desktop new file mode 100644 index 0000000000..2cd1bdd5c6 --- /dev/null +++ b/plugins/python/mixer_slider_docker/kritapykrita_mixer_slider_docker.desktop @@ -0,0 +1,26 @@ +[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[es]=Panel del deslizador del mezclador +Name[et]=Mikseri liuguri dokk +Name[nl]=Vastzetter van schuifregelaar van mixer +Name[pt]=Área da Barra de Mistura +Name[sv]=Dockningsfönster för blandningsreglage +Name[uk]=Бічна панель повзунка мікшера +Name[x-test]=xxMixer Slider dockerxx +Name[zh_TW]=混色滑動條工具面板 +Comment=A color slider. +Comment[ca]=Un control lliscant per al color. +Comment[es]=Deslizador de color. +Comment[et]=Värviliugur. +Comment[nl]=Een schuifregelaar voor kleur +Comment[pt]=Uma barra de cores. +Comment[sv]=Ett färgreglage. +Comment[uk]=Повзунок кольору. +Comment[x-test]=xxA color slider.xx +Comment[zh_TW]=色彩滑動條。 diff --git a/plugins/python/mixer_slider_docker/mixer_slider_docker.py b/plugins/python/mixer_slider_docker/mixer_slider_docker.py new file mode 100644 index 0000000000..e3d86bfba8 --- /dev/null +++ b/plugins/python/mixer_slider_docker/mixer_slider_docker.py @@ -0,0 +1,131 @@ +''' + Copyright (C) 2019 Tusooa Zhu + + This file is part of Krita-docker-color-slider. + + Krita-docker-color-slider is free software: you can 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. + + Krita-docker-color-slider is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Krita-docker-color-slider. If not, see . +''' +from PyQt5.QtGui import QColor +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton + +from krita import Krita, DockWidget, ManagedColor, DockWidgetFactory, DockWidgetFactoryBase + +from .slider_line import SliderLine +from .ui_mixer_slider_docker import UIMixerSliderDocker + + +class MixerSliderDocker(DockWidget): + # Init the docker + + def __init__(self): + super(MixerSliderDocker, self).__init__() + + main_program = Krita.instance() + settings = main_program.readSetting("", "MixerSliderColors", + "RGBA,U8,sRGB-elle-V2-srgbtrc.icc,1,0.8,0.4,1|" + + "RGBA,U8,sRGB-elle-V2-srgbtrc.icc,0,0,0,1") # alpha=1 == non-transparent + + self.default_left_color = self.qcolor_to_managedcolor(QColor.fromRgbF(0.4, 0.8, 1, 1)) + self.default_right_color = self.qcolor_to_managedcolor(QColor.fromRgbF(0, 0, 0, 1)) + + # make base-widget and layout + self.widget = QWidget() + self.sliders = [] + self.top_layout = QVBoxLayout() + self.main_layout = QHBoxLayout() + self.top_layout.addLayout(self.main_layout) + self.top_layout.addStretch(0) + self.settings_button = QPushButton() + icon = main_program.icon("configure") + self.settings_button.setIcon(icon) + self.settings_button.setToolTip(i18n('Change settings')) + self.settings_button.setMaximumSize(30, 30) + self.main_layout.addWidget(self.settings_button) + self.layout = QVBoxLayout() + self.layout.setSpacing(0) + self.main_layout.addLayout(self.layout) + for line in settings.split(";"): + colors = line.split('|') + if len(colors) < 2: # discard old configurations + continue + left_color = self.parse_color(colors[0].split(',')) + right_color = self.parse_color(colors[1].split(',')) + widget = SliderLine(left_color, right_color, self) + self.sliders.append(widget) + self.layout.addWidget(widget) + + self.widget.setLayout(self.top_layout) + self.setWindowTitle(i18n("Mixer Slider Docker")) + self.setWidget(self.widget) + [x.show() for x in self.sliders] + + self.settings_button.clicked.connect(self.init_ui) + + def settings_changed(self): + if self.ui.line_edit is not None: + num_sliders = int(self.ui.line_edit.text()) + if len(self.sliders) > num_sliders: + for extra_line in self.sliders[num_sliders:]: + self.layout.removeWidget(extra_line) + extra_line.setParent(None) + + self.sliders = self.sliders[0:num_sliders] + elif len(self.sliders) < num_sliders: + for i in range(num_sliders - len(self.sliders)): + widget = SliderLine(self.default_left_color, self.default_right_color, self) + self.sliders.append(widget) + self.layout.addWidget(widget) + self.write_settings() + + def get_color_space(self): + if self.canvas() is not None: + if self.canvas().view() is not None: + canvas_color = self.canvas().view().foregroundColor() + return ManagedColor(canvas_color.colorModel(), canvas_color.colorDepth(), canvas_color.colorProfile()) + return ManagedColor('RGBA', 'U8', 'sRGB-elle-V2-srgbtrc.icc') + + def init_ui(self): + self.ui = UIMixerSliderDocker() + self.ui.initialize(self) + + def write_settings(self): + main_program = Krita.instance() + setting = ';'.join( + [self.color_to_settings(line.left) + '|' + self.color_to_settings(line.right) + for line in self.sliders]) + + main_program.writeSetting("", "MixerSliderColors", setting) + + def color_to_settings(self, managedcolor): + return ','.join([managedcolor.colorModel(), + managedcolor.colorDepth(), + managedcolor.colorProfile()] + + [str(c) for c in managedcolor.components()]) + + def parse_color(self, array): + color = ManagedColor(array[0], array[1], array[2]) + color.setComponents([float(x) for x in array[3:]]) + return color + + def canvasChanged(self, canvas): + pass + + def qcolor_to_managedcolor(self, qcolor): + mc = ManagedColor.fromQColor(qcolor, self.canvas()) + return mc + + def managedcolor_to_qcolor(self, managedcolor): + return managedcolor.colorForCanvas(self.canvas()) + +Application.addDockWidgetFactory(DockWidgetFactory("mixer_slider_docker", DockWidgetFactoryBase.DockRight, MixerSliderDocker)) diff --git a/plugins/python/mixer_slider_docker/settings_dialog.py b/plugins/python/mixer_slider_docker/settings_dialog.py new file mode 100644 index 0000000000..a50b01016f --- /dev/null +++ b/plugins/python/mixer_slider_docker/settings_dialog.py @@ -0,0 +1,34 @@ +''' + Copyright (C) 2019 Tusooa Zhu + + This file is part of Krita-docker-color-slider. + + Krita-docker-color-slider is free software: you can 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. + + Krita-docker-color-slider is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Krita-docker-color-slider. If not, see . +''' +from PyQt5.QtWidgets import QDialog + + +class SettingsDialog(QDialog): + def __init__(self, ui_mixer_slider, parent=None): + super(SettingsDialog, self).__init__(parent) + + self.ui_mixer_slider = ui_mixer_slider + + def accept(self): + self.ui_mixer_slider.docker.settings_changed() + + super(SettingsDialog, self).accept() + + def closeEvent(self, event): + event.accept() diff --git a/plugins/python/mixer_slider_docker/slider_line.py b/plugins/python/mixer_slider_docker/slider_line.py new file mode 100644 index 0000000000..efad28e2c3 --- /dev/null +++ b/plugins/python/mixer_slider_docker/slider_line.py @@ -0,0 +1,103 @@ +''' + Copyright (C) 2019 Tusooa Zhu + + This file is part of Krita-docker-color-slider. + + Krita-docker-color-slider is free software: you can 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. + + Krita-docker-color-slider is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Krita-docker-color-slider. If not, see . +''' +from PyQt5.QtWidgets import QHBoxLayout, QWidget +from PyQt5.QtGui import QPixmap, QPainter +from PyQt5.Qt import pyqtSlot, pyqtSignal + +from .color_slider import ColorSlider + + +class SliderBtn(QWidget): + clicked = pyqtSignal() + + def __init__(self, parent=None): + super(SliderBtn, self).__init__(parent) + + def set_color(self, qcolor): + self.color = qcolor + self.update() + + def update_color(self): + color_sq = QPixmap(self.width(), self.height()) + color_sq.fill(self.color) + image = color_sq.toImage() + + painter = QPainter(self) + painter.drawImage(0, 0, image) + + def paintEvent(self, event): + self.update_color() + + def mouseReleaseEvent(self, event): + self.clicked.emit() + + +class SliderLine(QWidget): + def __init__(self, left_color, right_color, docker, parent=None): + super(SliderLine, self).__init__(parent) + self.left_button = SliderBtn() + self.right_button = SliderBtn() + self.docker = docker + self.color_slider = ColorSlider(docker) + self.layout = QHBoxLayout() + self.layout.setContentsMargins(2, 2, 2, 2) + self.setLayout(self.layout) + self.layout.addWidget(self.left_button) + self.layout.addWidget(self.color_slider) + self.layout.addWidget(self.right_button) + self.left_button.clicked.connect(self.slot_update_left_color) + self.right_button.clicked.connect(self.slot_update_right_color) + self.set_color('left', left_color) + self.set_color('right', right_color) + self.left_button.setMinimumSize(30, 30) + self.left_button.setMaximumSize(30, 30) + self.right_button.setMinimumSize(30, 30) + self.right_button.setMaximumSize(30, 30) + self.color_slider.setMaximumHeight(30) + + def set_color(self, pos, color): + button_to_set = None + if pos == 'left': + self.left = color + button_to_set = self.left_button + else: + self.right = color + button_to_set = self.right_button + + self.color_slider.set_color(pos, color) + + button_to_set.set_color(self.docker.managedcolor_to_qcolor(color)) + + @pyqtSlot() + def slot_update_left_color(self): + if self.docker.canvas() is not None: + if self.docker.canvas().view() is not None: + self.set_color('left', self.docker.canvas().view().foregroundColor()) + self.color_slider.value_x = 0 # set the cursor to the left-most + self.color_slider.update() + self.docker.write_settings() + + @pyqtSlot() + def slot_update_right_color(self): + if self.docker.canvas() is not None: + if self.docker.canvas().view() is not None: + self.set_color('right', self.docker.canvas().view().foregroundColor()) + self.color_slider.value_x = self.color_slider.width() - 1 + self.color_slider.update() + self.docker.write_settings() diff --git a/plugins/python/mixer_slider_docker/ui_mixer_slider_docker.py b/plugins/python/mixer_slider_docker/ui_mixer_slider_docker.py new file mode 100644 index 0000000000..92d0674a1c --- /dev/null +++ b/plugins/python/mixer_slider_docker/ui_mixer_slider_docker.py @@ -0,0 +1,56 @@ +''' + Copyright (C) 2019 Tusooa Zhu + + This file is part of Krita-docker-color-slider. + + Krita-docker-color-slider is free software: you can 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. + + Krita-docker-color-slider is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Krita-docker-color-slider. If not, see . +''' +from PyQt5.QtWidgets import QDialogButtonBox, QLabel, QVBoxLayout, QHBoxLayout, QSpinBox +from PyQt5.QtGui import QIntValidator +from PyQt5.QtCore import Qt +import krita + +from .settings_dialog import SettingsDialog + + +class UIMixerSliderDocker(object): + def __init__(self): + self.krita_instance = krita.Krita.instance() + self.main_dialog = SettingsDialog(self, self.krita_instance.activeWindow().qwindow()) + + self.button_box = QDialogButtonBox(self.main_dialog) + self.vbox = QVBoxLayout(self.main_dialog) + self.hbox = QHBoxLayout(self.main_dialog) + self.line_edit = None + + self.button_box.accepted.connect(self.main_dialog.accept) + self.button_box.rejected.connect(self.main_dialog.reject) + + self.button_box.setOrientation(Qt.Horizontal) + self.button_box.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + + def initialize(self, docker): + self.docker = docker + + self.vbox.addLayout(self.hbox) + self.hbox.addWidget(QLabel(i18n('Number of slider lines: '))) + self.line_edit = QSpinBox() + self.line_edit.setValue(len(docker.sliders)) + self.hbox.addWidget(self.line_edit) + + self.vbox.addWidget(self.button_box) + + self.main_dialog.show() + self.main_dialog.activateWindow() + self.main_dialog.exec_() diff --git a/plugins/python/palette_docker/kritapykrita_palette_docker.desktop b/plugins/python/palette_docker/kritapykrita_palette_docker.desktop index 2603892208..ba9eab094c 100644 --- a/plugins/python/palette_docker/kritapykrita_palette_docker.desktop +++ b/plugins/python/palette_docker/kritapykrita_palette_docker.desktop @@ -1,55 +1,59 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=palette_docker X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Palette docker Name[ar]=رصيف اللوحات Name[ca]=Acoblador Paleta Name[ca@valencia]=Acoblador Paleta Name[cs]=Dok palet Name[de]=Paletten-Docker Name[el]=Προσάρτηση παλέτας Name[en_GB]=Palette docker Name[es]=Panel de paleta +Name[et]=Paletidokk Name[eu]=Paleta-panela Name[fi]=Palettitelakka Name[fr]=Panneau de palette Name[gl]=Doca de paleta Name[is]=Tengikví fyrir litaspjald Name[it]=Area di aggancio della tavolozza Name[ko]=팔레트 도킹 패널 Name[nl]=Vastzetter van palet Name[nn]=Palettdokk Name[pl]=Dok palety Name[pt]=Área acoplável da paleta +Name[pt_BR]=Área da paleta Name[sv]=Dockningsfönster för palett Name[tr]=Palet doku Name[uk]=Панель палітри Name[x-test]=xxPalette dockerxx Name[zh_CN]=调色板工具面板 Name[zh_TW]=「調色盤」面板 Comment=A Python-based docker to edit color palettes. Comment[ar]=رصيف بِ‍«پيثون» لتحرير لوحات الألوان. Comment[ca]=Un acoblador basat en Python per editar paletes de colors. Comment[ca@valencia]=Un acoblador basat en Python per editar paletes de colors. Comment[el]=Ένα εργαλείο προσάρτησης σε Python για την επεξεργασία παλετών χρώματος. Comment[en_GB]=A Python-based docker to edit colour palettes. Comment[es]=Un panel basado en Python para editar paletas de colores. +Comment[et]=Pythoni-põhine dokk värvipalettide muutmiseks. Comment[eu]=Kolore-paletak editatzeko Python-oinarridun paleta bat. Comment[fi]=Python-pohjainen telakka väripalettien muokkaamiseen. Comment[fr]=Panneau en Python pour éditer les palettes de couleurs. Comment[gl]=Unha doca baseada en Python para editar paletas de cores. Comment[it]=Un'area di aggancio per modificare le tavolozze di colori basata su Python. Comment[ko]=색상 팔레트를 편집할 수 있는 Python 기반 도킹 패널입니다. Comment[nl]=Een op Python gebaseerde vastzetter om kleurpaletten te bewerken. Comment[nn]=Python-basert dokk for redigering av fargepalettar Comment[pl]=Dok oparty na pythonie do edytowania palet barw. Comment[pt]=Uma área acoplável, feita em Python, para editar paletas de cores. +Comment[pt_BR]=Uma área acoplável feita em Python para editar paletas de cores. Comment[sv]=Ett Python-baserat dockningsfönster för att redigera färgpaletter. Comment[tr]=Renk paletlerini düzenlemek için Python-tabanlı bir dok. Comment[uk]=Бічна панель для редагування палітр кольорів на основі Python. Comment[x-test]=xxA Python-based docker to edit color palettes.xx Comment[zh_CN]=基于 Python 的调色板编辑器工具面板 Comment[zh_TW]=基於 Python 的面板,用於編輯調色盤。 diff --git a/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop b/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop index 6dc82d0dda..2e8133a3fc 100644 --- a/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop +++ b/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop @@ -1,44 +1,48 @@ [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[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[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[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 86cce72c36..6b8ba2d988 100644 --- a/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop +++ b/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop @@ -1,53 +1,57 @@ [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-dokk Name[pl]=Dok szybkich ustawień Name[pt]=Área de Configuração Rápida +Name[pt_BR]=Área de configuração rápida Name[sv]=Dockningspanel med snabbinställningar Name[tr]=Hızlı Ayarlar Doku Name[uk]=Панель швидких параметрів Name[x-test]=xxQuick Settings Dockerxx Name[zh_CN]=快速设置工具面板 Name[zh_TW]=「快速設定」面板 Comment=A Python-based docker for quickly changing brush size and opacity. Comment[ar]=رصيف بِ‍«پيثون» لتغيير حجم الفرشاة وشفافيّتها بسرعة. Comment[ca]=Un acoblador basat en Python per a canviar ràpidament la mida i l'opacitat del pinzell. Comment[ca@valencia]=Un acoblador basat en Python per a canviar ràpidament la mida i l'opacitat del pinzell. Comment[el]=Ένα εργαλείο προσάρτησης σε Python για γρήγορη αλλαγή του μεγέθους και της αδιαφάνειας του πινέλου. Comment[en_GB]=A Python-based docker for quickly changing brush size and opacity. Comment[es]=Un panel basado en Python para cambiar rápidamente el tamaño y la opacidad del pincel. +Comment[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 dokk for kjapp endring av penselstorleik/-tettleik Comment[pl]=Dok oparty na Pythonie do szybkiej zmiany rozmiaru i nieprzezroczystości pędzla. Comment[pt]=Uma área acoplável em Python para mudar rapidamente o tamanho e opacidade do pincel. +Comment[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 b37503456d..de7e0cf2ae 100644 --- a/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop +++ b/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop @@ -1,50 +1,54 @@ [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]=Skriptdokk Name[pl]=Dok skryptów Name[pt]=Área de Programas +Name[pt_BR]=Área de scripts 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[gl]=Unha doca escrita en Python para crear accións e apuntar a scripts escritos en Python. Comment[it]=Un'area di aggancio basata su Python per creare azioni e scegliere script Python Comment[ko]=작업 생성 및 Python 스크립트를 가리키는 Python 기반 도킹 패널 Comment[nl]=Een op Python gebaseerde vastzetter voor aanmaakacties en wijzen naar Python-scripts Comment[nn]=Python-basert dokk for å laga handlingar og peika til Python-skript Comment[pl]=Dok oparty na pythonie do tworzenia działań i punktów dla skryptów pythona Comment[pt]=Uma área acoplável, feita em Python, para criar acções e apontar para programas em Python +Comment[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 e79dde80b6..d157294c5e 100644 --- a/plugins/python/scripter/kritapykrita_scripter.desktop +++ b/plugins/python/scripter/kritapykrita_scripter.desktop @@ -1,49 +1,53 @@ [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[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[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/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop b/plugins/python/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop index 1b463b43a6..b5075186db 100644 --- a/plugins/python/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop +++ b/plugins/python/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop @@ -1,50 +1,54 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=selectionsbagdocker X-Python-2-Compatible=false Name=Selections Bag Docker Name[ca]=Acoblador Bossa de seleccions Name[ca@valencia]=Acoblador Bossa de seleccions Name[el]=Προσάρτηση σάκου επιλογών Name[en_GB]=Selections Bag Docker Name[es]=Panel de selecciones +Name[et]=Valikukarbi dokk Name[eu]=Hautapen-zakua panela Name[fr]=Outils de sélection Name[gl]=Doca de bolsa das seleccións Name[it]=Area di raccolta selezioni Name[ko]=선택 가방 도킹 패널 Name[nl]=Docker van zak met selecties Name[nn]=Utvalssamlingsdokk Name[pl]=Dok worka zaznaczeń Name[pt]=Área de Selecções +Name[pt_BR]=Área de seleções Name[sv]=Dockningspanel med markeringspåse Name[tr]=Seçim Çantası Doku Name[uk]=Бічна панель позначеного Name[x-test]=xxSelections Bag Dockerxx Name[zh_CN]=选区列表工具面板 Name[zh_TW]=「選取範圍收藏」面板 Comment=A docker that allow to store a list of selections Comment[ar]=رصيف يتيح تخزين قائمة تحديدات Comment[ca]=Un acoblador que permet emmagatzemar una llista de seleccions Comment[ca@valencia]=Un acoblador que permet emmagatzemar una llista de seleccions Comment[cs]=Dok umožňující uložit seznam výběrů Comment[el]=Ένα εργαλείο προσάρτησης που επιτρέπει την αποθήκευση μιας λίστας επιλογών Comment[en_GB]=A docker that allow to store a list of selections Comment[es]=Un panel que permite guardar una lista de selecciones +Comment[et]=Dokk valikute salvestamiseks Comment[eu]=Hautapen zerrenda bat biltegiratzen uzten duen panel bat Comment[fi]=Telakka, joka sallii tallentaa valintaluettelon Comment[fr]=Panneau permettant de conserver une liste de sélections Comment[gl]=Unha doca que permite almacenar unha lista de seleccións. Comment[it]=Un'area di aggancio che consente di memorizzare un elenco di selezioni Comment[ko]=선택 목록을 저장할 수 있는 도킹 패널 Comment[nl]=Een docker die een lijst met selecties kan opslaan Comment[nn]=Dokk for lagring av ei liste med utval Comment[pl]=Dok, który umożliwia przechowywanie listy zaznaczeń Comment[pt]=Uma área acoplável que permite guardar uma lista de selecções +Comment[pt_BR]=Uma área acoplável que permite armazenar uma lista de seleções Comment[sv]=En dockningspanel som gör det möjligt att lagra en lista över markeringar Comment[tr]=Seçimlerin bir listesini saklamayı sağlayan bir dok Comment[uk]=Бічна панель, на якій можна зберігати список позначеного Comment[x-test]=xxA docker that allow to store a list of selectionsxx Comment[zh_CN]=用于保存一组选区的工具面板 Comment[zh_TW]=允許儲存選取範圍列表的面板 diff --git a/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop b/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop index bb8dd0a6bd..bd6c0ad96c 100644 --- a/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop +++ b/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop @@ -1,52 +1,56 @@ [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[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[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 16a93d1691..3f29e2c249 100644 --- a/plugins/python/tenscripts/kritapykrita_tenscripts.desktop +++ b/plugins/python/tenscripts/kritapykrita_tenscripts.desktop @@ -1,51 +1,55 @@ [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[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[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_colorpicker.cc b/plugins/tools/basictools/kis_tool_colorpicker.cc index 4795c1b243..19a090d52e 100644 --- a/plugins/tools/basictools/kis_tool_colorpicker.cc +++ b/plugins/tools/basictools/kis_tool_colorpicker.cc @@ -1,372 +1,421 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2002 Patrick Julien * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2018 Emmet & Eoin O'Neill * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_colorpicker.h" #include #include #include "kis_cursor.h" #include "KisDocument.h" #include "kis_canvas2.h" #include "KisReferenceImagesLayer.h" #include "KoCanvasBase.h" #include "kis_random_accessor_ng.h" #include "KoResourceServerProvider.h" #include #include "kis_wrapped_rect.h" #include "kis_tool_utils.h" namespace { // GUI ComboBox index constants const int SAMPLE_MERGED = 0; } KisToolColorPicker::KisToolColorPicker(KoCanvasBase *canvas) : KisTool(canvas, KisCursor::pickerCursor()), m_config(new KisToolUtils::ColorPickerConfig) { setObjectName("tool_colorpicker"); m_isActivated = false; m_optionsWidget = 0; m_pickedColor = KoColor(); + KoResourceServer *srv = KoResourceServerProvider::instance()->paletteServer(); + srv->addObserver(this); } KisToolColorPicker::~KisToolColorPicker() { if (m_isActivated) { m_config->save(m_toolActivationSource == KisTool::DefaultActivation); } + KoResourceServer *srv = KoResourceServerProvider::instance()->paletteServer(); + srv->removeObserver(this); } void KisToolColorPicker::paint(QPainter &gc, const KoViewConverter &converter) { Q_UNUSED(gc); Q_UNUSED(converter); } void KisToolColorPicker::activate(ToolActivation activation, const QSet &shapes) { m_isActivated = true; m_toolActivationSource = activation; m_config->load(m_toolActivationSource == KisTool::DefaultActivation); updateOptionWidget(); KisTool::activate(activation, shapes); } void KisToolColorPicker::deactivate() { m_config->save(m_toolActivationSource == KisTool::DefaultActivation); m_isActivated = false; KisTool::deactivate(); } bool KisToolColorPicker::pickColor(const QPointF &pos) { // Timer check. if (m_colorPickerDelayTimer.isActive()) { return false; } else { m_colorPickerDelayTimer.setSingleShot(true); m_colorPickerDelayTimer.start(100); } QScopedPointer> imageLocker; m_pickedColor.setOpacity(0.0); // Pick from reference images. if (m_optionsWidget->cmbSources->currentIndex() == SAMPLE_MERGED) { auto *kisCanvas = dynamic_cast(canvas()); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(kisCanvas, false); KisSharedPtr referenceImageLayer = kisCanvas->imageView()->document()->referenceImagesLayer(); if (referenceImageLayer && kisCanvas->referenceImagesDecoration()->visible()) { QColor color = referenceImageLayer->getPixel(pos); if (color.isValid()) { m_pickedColor.fromQColor(color); } } } if (m_pickedColor.opacityU8() == OPACITY_TRANSPARENT_U8) { if (!currentImage()->bounds().contains(pos.toPoint()) && !currentImage()->wrapAroundModePermitted()) { return false; } KisPaintDeviceSP dev; if (m_optionsWidget->cmbSources->currentIndex() != SAMPLE_MERGED && currentNode() && currentNode()->colorPickSourceDevice()) { dev = currentNode()->colorPickSourceDevice(); } else { imageLocker.reset(new boost::lock_guard(*currentImage())); dev = currentImage()->projection(); } KoColor previousColor = canvas()->resourceManager()->foregroundColor(); KisToolUtils::pickColor(m_pickedColor, dev, pos.toPoint(), &previousColor, m_config->radius, m_config->blend); } if (m_config->updateColor && m_pickedColor.opacityU8() != OPACITY_TRANSPARENT_U8) { KoColor publicColor = m_pickedColor; publicColor.setOpacity(OPACITY_OPAQUE_U8); // Alpha is unwanted for FG and BG colors. if (m_config->toForegroundColor) { canvas()->resourceManager()->setResource(KoCanvasResourceProvider::ForegroundColor, publicColor); } else { canvas()->resourceManager()->setResource(KoCanvasResourceProvider::BackgroundColor, publicColor); } } return true; } void KisToolColorPicker::beginPrimaryAction(KoPointerEvent *event) { bool sampleMerged = m_optionsWidget->cmbSources->currentIndex() == SAMPLE_MERGED; if (!sampleMerged) { if (!currentNode()) { QMessageBox::information(0, i18nc("@title:window", "Krita"), i18n("Cannot pick a color as no layer is active.")); event->ignore(); return; } if (!currentNode()->visible()) { QMessageBox::information(0, i18nc("@title:window", "Krita"), i18n("Cannot pick a color as the active layer is not visible.")); event->ignore(); return; } } QPoint pos = convertToImagePixelCoordFloored(event); setMode(KisTool::PAINT_MODE); bool picked = pickColor(pos); if (!picked) { // Color picking has to start in the visible part of the layer event->ignore(); return; } displayPickedColor(); } void KisToolColorPicker::continuePrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); QPoint pos = convertToImagePixelCoordFloored(event); pickColor(pos); displayPickedColor(); } #include "kis_display_color_converter.h" void KisToolColorPicker::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); if (m_config->addColorToCurrentPalette) { KisSwatch swatch; swatch.setColor(m_pickedColor); // We don't ask for a name, too intrusive here - KoColorSet *palette = m_palettes.at(m_optionsWidget->cmbPalette->currentIndex()); + KoColorSet *palette = m_palettes.at(m_optionsWidget->cmbPalette->currentIndex()); palette->add(swatch); if (!palette->save()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Cannot write to palette file %1. Maybe it is read-only.", palette->filename())); - } + } } } struct PickedChannel { QString name; QString valueText; }; void KisToolColorPicker::displayPickedColor() { if (m_pickedColor.data() && m_optionsWidget) { QList channels = m_pickedColor.colorSpace()->channels(); m_optionsWidget->listViewChannels->clear(); QVector pickedChannels; for (int i = 0; i < channels.count(); ++i) { pickedChannels.append(PickedChannel()); } for (int i = 0; i < channels.count(); ++i) { PickedChannel pc; pc.name = channels[i]->name(); if (m_config->normaliseValues) { pc.valueText = m_pickedColor.colorSpace()->normalisedChannelValueText(m_pickedColor.data(), i); } else { pc.valueText = m_pickedColor.colorSpace()->channelValueText(m_pickedColor.data(), i); } pickedChannels[channels[i]->displayPosition()] = pc; } Q_FOREACH (const PickedChannel &pc, pickedChannels) { QTreeWidgetItem *item = new QTreeWidgetItem(m_optionsWidget->listViewChannels); item->setText(0, pc.name); item->setText(1, pc.valueText); } KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); KoColor newColor = kritaCanvas->displayColorConverter()->applyDisplayFiltering(m_pickedColor, Float32BitsColorDepthID); QVector values(4); newColor.colorSpace()->normalisedChannelsValue(newColor.data(), values); for (int i = 0; i < values.size(); i++) { QTreeWidgetItem *item = new QTreeWidgetItem(m_optionsWidget->listViewChannels); item->setText(0, QString("DisplayCh%1").arg(i)); item->setText(1, QString::number(values[i])); } } } QWidget* KisToolColorPicker::createOptionWidget() { m_optionsWidget = new ColorPickerOptionsWidget(0); m_optionsWidget->setObjectName(toolId() + " option widget"); m_optionsWidget->listViewChannels->setSortingEnabled(false); // 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); // Initialize blend KisSliderSpinBox m_optionsWidget->blend->setRange(0,100); m_optionsWidget->blend->setSuffix(i18n("%")); updateOptionWidget(); connect(m_optionsWidget->cbUpdateCurrentColor, SIGNAL(toggled(bool)), SLOT(slotSetUpdateColor(bool))); connect(m_optionsWidget->cbNormaliseValues, SIGNAL(toggled(bool)), SLOT(slotSetNormaliseValues(bool))); connect(m_optionsWidget->cbPalette, SIGNAL(toggled(bool)), SLOT(slotSetAddPalette(bool))); connect(m_optionsWidget->radius, SIGNAL(valueChanged(int)), SLOT(slotChangeRadius(int))); connect(m_optionsWidget->blend, SIGNAL(valueChanged(int)), SLOT(slotChangeBlend(int))); connect(m_optionsWidget->cmbSources, SIGNAL(currentIndexChanged(int)), SLOT(slotSetColorSource(int))); KoResourceServer *srv = KoResourceServerProvider::instance()->paletteServer(); if (!srv) { return m_optionsWidget; } QList palettes = srv->resources(); Q_FOREACH (KoColorSet *palette, palettes) { if (palette) { m_optionsWidget->cmbPalette->addSqueezedItem(palette->name()); m_palettes.append(palette); } } return m_optionsWidget; } void KisToolColorPicker::updateOptionWidget() { if (!m_optionsWidget) return; m_optionsWidget->cbNormaliseValues->setChecked(m_config->normaliseValues); m_optionsWidget->cbUpdateCurrentColor->setChecked(m_config->updateColor); m_optionsWidget->cmbSources->setCurrentIndex(SAMPLE_MERGED + !m_config->sampleMerged); m_optionsWidget->cbPalette->setChecked(m_config->addColorToCurrentPalette); m_optionsWidget->radius->setValue(m_config->radius); m_optionsWidget->blend->setValue(m_config->blend); } void KisToolColorPicker::setToForeground(bool newValue) { m_config->toForegroundColor = newValue; emit toForegroundChanged(); } bool KisToolColorPicker::toForeground() const { return m_config->toForegroundColor; } void KisToolColorPicker::slotSetUpdateColor(bool state) { m_config->updateColor = state; } void KisToolColorPicker::slotSetNormaliseValues(bool state) { m_config->normaliseValues = state; displayPickedColor(); } void KisToolColorPicker::slotSetAddPalette(bool state) { m_config->addColorToCurrentPalette = state; } void KisToolColorPicker::slotChangeRadius(int value) { m_config->radius = value; } void KisToolColorPicker::slotChangeBlend(int value) { m_config->blend = value; } void KisToolColorPicker::slotSetColorSource(int value) { m_config->sampleMerged = value == SAMPLE_MERGED; } -void KisToolColorPicker::slotAddPalette(KoResource *resource) +void KisToolColorPicker::unsetResourceServer() { - KoColorSet *palette = dynamic_cast(resource); - if (palette) { - m_optionsWidget->cmbPalette->addSqueezedItem(palette->name()); - m_palettes.append(palette); + KoResourceServer *srv = KoResourceServerProvider::instance()->paletteServer(); + srv->removeObserver(this); +} + +void KisToolColorPicker::resourceAdded(KoColorSet* resource){ + if(!resource || !m_optionsWidget) return; + if(m_palettes.contains(resource)) return; + + if(m_config->addColorToCurrentPalette){ + updateCmbPalette(); + } +} + +void KisToolColorPicker::removingResource(KoColorSet* resource){ + if(!resource || !m_optionsWidget) return; + if(!m_palettes.contains(resource)) return; + + if(m_config->addColorToCurrentPalette){ + updateCmbPalette(); + } +} + +void KisToolColorPicker::updateCmbPalette(){ + m_optionsWidget->cmbPalette->clear(); + m_palettes.clear(); + + KoResourceServer *srv = KoResourceServerProvider::instance()->paletteServer(); + + if (!srv) { + return ; } + + QList palettes = srv->resources(); + + Q_FOREACH (KoColorSet *palette, palettes) { + if (palette) { + m_optionsWidget->cmbPalette->addSqueezedItem(palette->name()); + m_palettes.append(palette); + } + } + } + +void KisToolColorPicker::resourceChanged(PointerType /*resource*/) +{} + +void KisToolColorPicker::syncTaggedResourceView() {} + +void KisToolColorPicker::syncTagAddition(const QString& /*tag*/) {} + +void KisToolColorPicker::syncTagRemoval(const QString& /*tag*/) {} \ No newline at end of file diff --git a/plugins/tools/basictools/kis_tool_colorpicker.h b/plugins/tools/basictools/kis_tool_colorpicker.h index ce485f1995..a7c078570e 100644 --- a/plugins/tools/basictools/kis_tool_colorpicker.h +++ b/plugins/tools/basictools/kis_tool_colorpicker.h @@ -1,142 +1,152 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2002 Patrick Julien * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2018 Emmet & Eoin O'Neill * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TOOL_COLOR_PICKER_H_ #define KIS_TOOL_COLOR_PICKER_H_ #include #include "KoToolFactoryBase.h" #include "ui_wdgcolorpicker.h" #include "kis_tool.h" #include +#include + class KoResource; class KoColorSet; namespace KisToolUtils { struct ColorPickerConfig; } class ColorPickerOptionsWidget : public QWidget, public Ui::ColorPickerOptionsWidget { Q_OBJECT public: ColorPickerOptionsWidget(QWidget *parent) : QWidget(parent) { setupUi(this); } }; -class KisToolColorPicker : public KisTool +class KisToolColorPicker : public KisTool,public KoResourceServerObserver { Q_OBJECT Q_PROPERTY(bool toForeground READ toForeground WRITE setToForeground NOTIFY toForegroundChanged) public: KisToolColorPicker(KoCanvasBase *canvas); ~KisToolColorPicker() override; public: struct Configuration { Configuration(); bool toForegroundColor; bool updateColor; bool addPalette; bool normaliseValues; bool sampleMerged; int radius; int blend; void save(ToolActivation activation) const; void load(ToolActivation activation); }; public: QWidget* createOptionWidget() override; void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; void paint(QPainter &gc, const KoViewConverter &converter) override; bool toForeground() const; +public: //KoResourceServerObserver + void unsetResourceServer() override; + void resourceAdded(KoColorSet* resource) override; + void removingResource(KoColorSet* resource) override; + void resourceChanged(KoColorSet* resource) override; + void syncTaggedResourceView() override; + void syncTagAddition(const QString& tag) override; + void syncTagRemoval(const QString& tag) override; + Q_SIGNALS: void toForegroundChanged(); protected: void activate(ToolActivation activation, const QSet &) override; void deactivate() override; public Q_SLOTS: void setToForeground(bool newValue); void slotSetUpdateColor(bool); void slotSetNormaliseValues(bool); void slotSetAddPalette(bool); void slotChangeRadius(int); void slotChangeBlend(int); - void slotAddPalette(KoResource* resource); void slotSetColorSource(int value); private: void displayPickedColor(); bool pickColor(const QPointF& pos); void updateOptionWidget(); - + void updateCmbPalette(); // Configuration QScopedPointer m_config; ToolActivation m_toolActivationSource; bool m_isActivated; KoColor m_pickedColor; // Used to skip some tablet events and update color less often QTimer m_colorPickerDelayTimer; ColorPickerOptionsWidget *m_optionsWidget; QList m_palettes; }; class KisToolColorPickerFactory : public KoToolFactoryBase { public: KisToolColorPickerFactory() : KoToolFactoryBase("KritaSelected/KisToolColorPicker") { setToolTip(i18n("Color Selector Tool")); setSection(TOOL_TYPE_FILL); setPriority(2); setIconName(koIconNameCStr("krita_tool_color_picker")); setShortcut(QKeySequence(Qt::Key_P)); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); } ~KisToolColorPickerFactory() override {} KoToolBase *createTool(KoCanvasBase *canvas) override { return new KisToolColorPicker(canvas); } }; #endif // KIS_TOOL_COLOR_PICKER_H_ diff --git a/plugins/tools/basictools/kis_tool_line_helper.cpp b/plugins/tools/basictools/kis_tool_line_helper.cpp index 28a268a608..f823469b3e 100644 --- a/plugins/tools/basictools/kis_tool_line_helper.cpp +++ b/plugins/tools/basictools/kis_tool_line_helper.cpp @@ -1,190 +1,262 @@ /* * 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_tool_line_helper.h" +#include + #include "kis_algebra_2d.h" #include "kis_painting_information_builder.h" #include "kis_image.h" +#include "kis_canvas_resource_provider.h" +#include + struct KisToolLineHelper::Private { Private(KisPaintingInformationBuilder *_infoBuilder) : infoBuilder(_infoBuilder), useSensors(true), enabled(true) { } QVector linePoints; KisPaintingInformationBuilder *infoBuilder; bool useSensors; bool enabled; }; KisToolLineHelper::KisToolLineHelper(KisPaintingInformationBuilder *infoBuilder, const KUndo2MagicString &transactionText) : KisToolFreehandHelper(infoBuilder, transactionText, new KisSmoothingOptions(false)), m_d(new Private(infoBuilder)) { } KisToolLineHelper::~KisToolLineHelper() { delete m_d; } void KisToolLineHelper::setEnabled(bool value) { m_d->enabled = value; } void KisToolLineHelper::setUseSensors(bool value) { m_d->useSensors = value; } void KisToolLineHelper::repaintLine(KoCanvasResourceProvider *resourceManager, KisImageWSP image, KisNodeSP node, KisStrokesFacade *strokesFacade) { if (!m_d->enabled) return; cancelPaint(); if (m_d->linePoints.isEmpty()) return; qreal startAngle = 0.0; if (m_d->linePoints.length() > 1) { startAngle = KisAlgebra2D::directionBetweenPoints(m_d->linePoints[0].pos(), m_d->linePoints[1].pos(), 0.0); } + + KisPaintOpPresetSP preset = resourceManager->resource(KisCanvasResourceProvider::CurrentPaintOpPreset) + .value(); + + if (preset->settings()->paintOpSize() <= 1) { + KisPaintInformation begin = m_d->linePoints.first(); + KisPaintInformation end = m_d->linePoints.last(); + m_d->linePoints.clear(); + m_d->linePoints.append(begin); + m_d->linePoints.append(end); + } + // Always adjust line sections to avoid jagged sections. + adjustPointsToDDA(m_d->linePoints); + QVector::const_iterator it = m_d->linePoints.constBegin(); QVector::const_iterator end = m_d->linePoints.constEnd(); initPaintImpl(startAngle, *it, resourceManager, image, node, strokesFacade); ++it; while (it != end) { paintLine(*(it - 1), *it); ++it; } } void KisToolLineHelper::start(KoPointerEvent *event, KoCanvasResourceProvider *resourceManager) { if (!m_d->enabled) return; // Ignore the elapsed stroke time, so that the line tool will behave as if the whole stroke is // drawn at once. This should prevent any possible spurious dabs caused by airbrushing features. KisPaintInformation pi = m_d->infoBuilder->startStroke(event, 0, resourceManager); if (!m_d->useSensors) { pi = KisPaintInformation(pi.pos()); } m_d->linePoints.append(pi); } void KisToolLineHelper::addPoint(KoPointerEvent *event, const QPointF &overridePos) { if (!m_d->enabled) return; // Ignore the elapsed stroke time, so that the line tool will behave as if the whole stroke is // drawn at once. This should prevent any possible spurious dabs caused by airbrushing features. KisPaintInformation pi = m_d->infoBuilder->continueStroke(event, 0); if (!m_d->useSensors) { pi = KisPaintInformation(pi.pos()); } if (!overridePos.isNull()) { pi.setPos(overridePos); } if (m_d->linePoints.size() > 1) { const QPointF startPos = m_d->linePoints.first().pos(); const QPointF endPos = pi.pos(); const qreal maxDistance = kisDistance(startPos, endPos); const QPointF unit = (endPos - startPos) / maxDistance; QVector::iterator it = m_d->linePoints.begin(); ++it; while (it != m_d->linePoints.end()) { qreal dist = kisDistance(startPos, it->pos()); if (dist < maxDistance) { QPointF pos = startPos + unit * dist; it->setPos(pos); ++it; } else { it = m_d->linePoints.erase(it); } } } m_d->linePoints.append(pi); } void KisToolLineHelper::translatePoints(const QPointF &offset) { if (!m_d->enabled) return; QVector::iterator it = m_d->linePoints.begin(); while (it != m_d->linePoints.end()) { it->setPos(it->pos() + offset); ++it; } } void KisToolLineHelper::end() { if (!m_d->enabled) return; KIS_ASSERT_RECOVER_RETURN(isRunning()); endPaint(); clearPoints(); } void KisToolLineHelper::cancel() { if (!m_d->enabled) return; KIS_ASSERT_RECOVER_RETURN(isRunning()); cancelPaint(); clearPoints(); } void KisToolLineHelper::clearPoints() { m_d->linePoints.clear(); } void KisToolLineHelper::clearPaint() { if (!m_d->enabled) return; cancelPaint(); } + +void KisToolLineHelper::adjustPointsToDDA(QVector &points) +{ + int x = qFloor(points.first().pos().x()); + int y = qFloor(points.first().pos().y()); + + int x2 = qFloor(points.last().pos().x()); + int y2 = qFloor(points.last().pos().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; + int dist; + + if (fabs(m) > 1.0f) { + inc = (yd > 0) ? 1 : -1; + m = (lockAxis)? 0 : 1.0f / m; + m *= inc; + + for (int i = 0; i < points.size(); i++){ + dist = abs(qFloor(points.at(i).pos().y()) - y); + fy = y + (dist * inc); + fx = qRound(x + (dist * m)); + points[i].setPos(QPointF(fx,fy)); + } + + } else { + inc = (xd > 0) ? 1 : -1; + m *= inc; + + for (int i = 0; i < points.size(); i++){ + dist = abs(qFloor(points.at(i).pos().x()) - x); + fx = x + (dist * inc); + fy = qRound(y + (dist * m)); + points[i].setPos(QPointF(fx,fy)); + } + } +} diff --git a/plugins/tools/basictools/kis_tool_line_helper.h b/plugins/tools/basictools/kis_tool_line_helper.h index 13e3863e2c..c8cad7907a 100644 --- a/plugins/tools/basictools/kis_tool_line_helper.h +++ b/plugins/tools/basictools/kis_tool_line_helper.h @@ -1,56 +1,59 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_TOOL_LINE_HELPER_H #define __KIS_TOOL_LINE_HELPER_H #include "kis_tool_freehand_helper.h" class KisToolLineHelper : private KisToolFreehandHelper { public: KisToolLineHelper(KisPaintingInformationBuilder *infoBuilder, const KUndo2MagicString &transactionText); ~KisToolLineHelper() override; void setEnabled(bool value); void setUseSensors(bool value); void repaintLine(KoCanvasResourceProvider *resourceManager, KisImageWSP image, KisNodeSP node, KisStrokesFacade *strokesFacade); void start(KoPointerEvent *event, KoCanvasResourceProvider *resourceManager); void addPoint(KoPointerEvent *event, const QPointF &overridePos = QPointF()); void translatePoints(const QPointF &offset); void end(); void cancel(); void clearPoints(); void clearPaint(); using KisToolFreehandHelper::isRunning; +private: + void adjustPointsToDDA(QVector &points); + private: struct Private; Private * const m_d; }; #endif /* __KIS_TOOL_LINE_HELPER_H */ diff --git a/plugins/tools/basictools/kis_tool_multihand.cpp b/plugins/tools/basictools/kis_tool_multihand.cpp index dae752884f..48aba81fe6 100644 --- a/plugins/tools/basictools/kis_tool_multihand.cpp +++ b/plugins/tools/basictools/kis_tool_multihand.cpp @@ -1,605 +1,607 @@ /* * Copyright (c) 2011 Lukáš Tvrdý * 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_tool_multihand.h" #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_cursor.h" #include "kis_tool_multihand_helper.h" static const int MAXIMUM_BRUSHES = 50; #include #ifdef Q_OS_WIN // quoting DRAND48(3) man-page: // These functions are declared obsolete by SVID 3, // which states that rand(3) should be used instead. #define drand48() (static_cast(qrand()) / static_cast(RAND_MAX)) #endif KisToolMultihand::KisToolMultihand(KoCanvasBase *canvas) : KisToolBrush(canvas), m_transformMode(SYMMETRY), m_angle(0), m_handsCount(6), m_mirrorVertically(false), m_mirrorHorizontally(false), m_showAxes(false), m_translateRadius(100), m_setupAxesFlag(false), m_addSubbrushesMode(false) , customUI(0) { m_helper = new KisToolMultihandHelper(paintingInformationBuilder(), kundo2_i18n("Multibrush Stroke")); resetHelper(m_helper); if (image()) { m_axesPoint = QPointF(0.5 * image()->width(), 0.5 * image()->height()); } } KisToolMultihand::~KisToolMultihand() { } void KisToolMultihand::beginPrimaryAction(KoPointerEvent *event) { if(m_setupAxesFlag) { setMode(KisTool::OTHER); m_axesPoint = convertToPixelCoord(event->point); requestUpdateOutline(event->point, 0); updateCanvas(); } else if (m_addSubbrushesMode){ QPointF newPoint = convertToPixelCoord(event->point); m_subbrOriginalLocations << newPoint; requestUpdateOutline(event->point, 0); updateCanvas(); } else { initTransformations(); KisToolFreehand::beginPrimaryAction(event); } } void KisToolMultihand::continuePrimaryAction(KoPointerEvent *event) { if(mode() == KisTool::OTHER) { m_axesPoint = convertToPixelCoord(event->point); requestUpdateOutline(event->point, 0); updateCanvas(); } else { requestUpdateOutline(event->point, 0); KisToolFreehand::continuePrimaryAction(event); } } void KisToolMultihand::endPrimaryAction(KoPointerEvent *event) { if(mode() == KisTool::OTHER) { setMode(KisTool::HOVER_MODE); requestUpdateOutline(event->point, 0); finishAxesSetup(); } else { KisToolFreehand::endPrimaryAction(event); } } void KisToolMultihand::beginAlternateAction(KoPointerEvent* event, AlternateAction action) { if (action != ChangeSize || m_transformMode != COPYTRANSLATE || !m_addSubbrushesMode) { KisToolBrush::beginAlternateAction(event, action); return; } setMode(KisTool::OTHER_1); m_axesPoint = convertToPixelCoord(event->point); requestUpdateOutline(event->point, 0); updateCanvas(); } void KisToolMultihand::continueAlternateAction(KoPointerEvent* event, AlternateAction action) { if (action != ChangeSize || m_transformMode != COPYTRANSLATE || !m_addSubbrushesMode) { KisToolBrush::continueAlternateAction(event, action); return; } if (mode() == KisTool::OTHER_1) { m_axesPoint = convertToPixelCoord(event->point); requestUpdateOutline(event->point, 0); updateCanvas(); } } void KisToolMultihand::endAlternateAction(KoPointerEvent* event, AlternateAction action) { if (action != ChangeSize || m_transformMode != COPYTRANSLATE || !m_addSubbrushesMode) { KisToolBrush::endAlternateAction(event, action); return; } if (mode() == KisTool::OTHER_1) { setMode(KisTool::HOVER_MODE); } } void KisToolMultihand::mouseMoveEvent(KoPointerEvent* event) { if (mode() == HOVER_MODE) { m_lastToolPos=convertToPixelCoord(event->point); } KisToolBrush::mouseMoveEvent(event); } void KisToolMultihand::paint(QPainter& gc, const KoViewConverter &converter) { QPainterPath path; if (m_showAxes) { int axisLength = currentImage()->height() + currentImage()->width(); // add division guide lines if using multiple brushes if ((m_handsCount > 1 && m_transformMode == SYMMETRY) || (m_handsCount > 1 && m_transformMode == SNOWFLAKE) ) { int axesCount; if (m_transformMode == SYMMETRY){ axesCount = m_handsCount; } else { axesCount = m_handsCount*2; } qreal axesAngle = 360.0 / float(axesCount); float currentAngle = 0.0; float startingInsetLength = 20; // don't start each line at the origin so we can see better when all points converge // draw lines radiating from the origin for( int i=0; i < axesCount; i++) { currentAngle = i*axesAngle; // convert angles to radians since cos and sin need that currentAngle = currentAngle * 0.017453 + m_angle; // m_angle is current rotation set on UI QPoint startingSpot = QPoint(m_axesPoint.x()+ (sin(currentAngle)*startingInsetLength), m_axesPoint.y()- (cos(currentAngle))*startingInsetLength ); path.moveTo(startingSpot.x(), startingSpot.y()); QPointF symmetryLinePoint(m_axesPoint.x()+ (sin(currentAngle)*axisLength), m_axesPoint.y()- (cos(currentAngle))*axisLength ); path.lineTo(symmetryLinePoint); } } else if(m_transformMode == MIRROR) { if (m_mirrorHorizontally) { path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()-axisLength*sin(m_angle+M_PI_2)); path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()+axisLength*sin(m_angle+M_PI_2)); } if(m_mirrorVertically) { path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle), m_axesPoint.y()-axisLength*sin(m_angle)); path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle), m_axesPoint.y()+axisLength*sin(m_angle)); } } else if (m_transformMode == COPYTRANSLATE) { int ellipsePreviewSize = 10; // draw ellipse at origin to emphasize this is a drawing point path.addEllipse(m_axesPoint.x()-(ellipsePreviewSize), m_axesPoint.y()-(ellipsePreviewSize), ellipsePreviewSize*2, ellipsePreviewSize*2); for (QPointF dPos : m_subbrOriginalLocations) { path.addEllipse(dPos, ellipsePreviewSize, ellipsePreviewSize); // Show subbrush reference locations while in add mode } // draw the horiz/vertical line for axis origin path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle), m_axesPoint.y()-axisLength*sin(m_angle)); path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle), m_axesPoint.y()+axisLength*sin(m_angle)); path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()-axisLength*sin(m_angle+M_PI_2)); path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()+axisLength*sin(m_angle+M_PI_2)); } else { // draw the horiz/vertical line for axis origin path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle), m_axesPoint.y()-axisLength*sin(m_angle)); path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle), m_axesPoint.y()+axisLength*sin(m_angle)); path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()-axisLength*sin(m_angle+M_PI_2)); path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()+axisLength*sin(m_angle+M_PI_2)); } } else { // not showing axis if (m_transformMode == COPYTRANSLATE) { for (QPointF dPos : m_subbrOriginalLocations) { // Show subbrush reference locations while in add mode if (m_addSubbrushesMode) { path.addEllipse(dPos, 10, 10); } } } } KisToolFreehand::paint(gc, converter); // origin point preview line/s gc.save(); QPen outlinePen; outlinePen.setColor(QColor(100,100,100,150)); outlinePen.setStyle(Qt::PenStyle::SolidLine); gc.setPen(outlinePen); paintToolOutline(&gc, pixelToView(path)); gc.restore(); // fill in a dot for the origin if showing axis if (m_showAxes) { // draw a dot at the origin point to help with precisly moving QPainterPath dotPath; int dotRadius = 4; dotPath.moveTo(m_axesPoint.x(), m_axesPoint.y()); dotPath.addEllipse(m_axesPoint.x()- dotRadius*0.25, m_axesPoint.y()- dotRadius*0.25, dotRadius, dotRadius); // last 2 parameters are dot's size QBrush fillBrush; fillBrush.setColor(QColor(255, 255, 255, 255)); fillBrush.setStyle(Qt::SolidPattern); gc.fillPath(pixelToView(dotPath), fillBrush); // add slight offset circle for contrast to help show it on dotPath = QPainterPath(); // resets path dotPath.addEllipse(m_axesPoint.x() - dotRadius*0.75, m_axesPoint.y()- dotRadius*0.75, dotRadius, dotRadius); // last 2 parameters are dot's size fillBrush.setColor(QColor(120, 120, 120, 255)); gc.fillPath(pixelToView(dotPath), fillBrush); } } void KisToolMultihand::initTransformations() { QVector transformations; QTransform m; if(m_transformMode == SYMMETRY) { qreal angle = 0; qreal angleStep = (2 * M_PI) / m_handsCount; for(int i = 0; i < m_handsCount; i++) { m.translate(m_axesPoint.x(), m_axesPoint.y()); m.rotateRadians(angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); angle += angleStep; } } else if(m_transformMode == MIRROR) { transformations << m; if (m_mirrorHorizontally) { m.translate(m_axesPoint.x(),m_axesPoint.y()); m.rotateRadians(m_angle); m.scale(-1,1); m.rotateRadians(-m_angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); } if (m_mirrorVertically) { m.translate(m_axesPoint.x(),m_axesPoint.y()); m.rotateRadians(m_angle); m.scale(1,-1); m.rotateRadians(-m_angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); } if (m_mirrorVertically && m_mirrorHorizontally){ m.translate(m_axesPoint.x(),m_axesPoint.y()); m.rotateRadians(m_angle); m.scale(-1,-1); m.rotateRadians(-m_angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); } } else if(m_transformMode == SNOWFLAKE) { qreal angle = 0; qreal angleStep = (2 * M_PI) / m_handsCount/4; for(int i = 0; i < m_handsCount*4; i++) { if ((i%2)==1) { m.translate(m_axesPoint.x(), m_axesPoint.y()); m.rotateRadians(m_angle-angleStep); m.rotateRadians(angle); m.scale(-1,1); m.rotateRadians(-m_angle+angleStep); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); angle += angleStep*2; } else { m.translate(m_axesPoint.x(), m_axesPoint.y()); m.rotateRadians(m_angle-angleStep); m.rotateRadians(angle); m.rotateRadians(-m_angle+angleStep); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); angle += angleStep*2; } } } else if(m_transformMode == TRANSLATE) { /** * TODO: currently, the seed is the same for all the * strokes */ for (int i = 0; i < m_handsCount; i++){ qreal angle = drand48() * M_PI * 2; qreal length = drand48(); // convert the Polar coordinates to Cartesian coordinates qreal nx = (m_translateRadius * cos(angle) * length); qreal ny = (m_translateRadius * sin(angle) * length); m.translate(m_axesPoint.x(),m_axesPoint.y()); m.rotateRadians(m_angle); m.translate(nx,ny); m.rotateRadians(-m_angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); } } else if (m_transformMode == COPYTRANSLATE) { transformations << m; for (QPointF dPos : m_subbrOriginalLocations) { QPointF resPos = dPos-m_axesPoint; // Calculate the difference between subbrush reference position and "origin" reference m.translate(resPos.x(), resPos.y()); transformations << m; m.reset(); } } m_helper->setupTransformations(transformations); } QWidget* KisToolMultihand::createOptionWidget() { QWidget *widget = KisToolBrush::createOptionWidget(); customUI = new KisToolMultiHandConfigWidget(); // brush smoothing option. //customUI->layout()->addWidget(widget); customUI->smoothingOptionsLayout->addWidget(widget); // setup common parameters that all of the modes will see connect(customUI->showAxesCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetAxesVisible(bool))); customUI->showAxesCheckbox->setChecked((bool)m_configGroup.readEntry("showAxes", false)); connect(image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SLOT(resetAxes())); customUI->moveOriginButton->setCheckable(true); connect(customUI->moveOriginButton, SIGNAL(clicked(bool)),this, SLOT(activateAxesPointModeSetup())); connect(customUI->resetOriginButton, SIGNAL(released()), this, SLOT(resetAxes())); customUI->multihandTypeCombobox->addItem(i18n("Symmetry"),int(SYMMETRY)); // axis mode customUI->multihandTypeCombobox->addItem(i18n("Mirror"),int(MIRROR)); customUI->multihandTypeCombobox->addItem(i18n("Translate"),int(TRANSLATE)); customUI->multihandTypeCombobox->addItem(i18n("Snowflake"),int(SNOWFLAKE)); customUI->multihandTypeCombobox->addItem(i18n("Copy Translate"),int(COPYTRANSLATE)); connect(customUI->multihandTypeCombobox,SIGNAL(currentIndexChanged(int)),this, SLOT(slotSetTransformMode(int))); customUI->multihandTypeCombobox->setCurrentIndex(m_configGroup.readEntry("transformMode", 0)); slotSetTransformMode(customUI->multihandTypeCombobox->currentIndex()); customUI->axisRotationSpinbox->setSuffix(QChar(Qt::Key_degree)); // origin rotation customUI->axisRotationSpinbox->setSingleStep(0.5); customUI->axisRotationSpinbox->setRange(0.0, 90.0, 1); customUI->axisRotationSpinbox->setValue(m_configGroup.readEntry("axesAngle", 0.0)); connect( customUI->axisRotationSpinbox, SIGNAL(valueChanged(qreal)),this, SLOT(slotSetAxesAngle(qreal))); // symmetry mode options customUI->brushCountSpinBox->setRange(1, MAXIMUM_BRUSHES); connect(customUI->brushCountSpinBox, SIGNAL(valueChanged(int)),this, SLOT(slotSetHandsCount(int))); customUI->brushCountSpinBox->setValue(m_configGroup.readEntry("handsCount", 4)); // mirror mode specific options connect(customUI->horizontalCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetMirrorHorizontally(bool))); customUI->horizontalCheckbox->setChecked((bool)m_configGroup.readEntry("mirrorHorizontally", false)); connect(customUI->verticalCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetMirrorVertically(bool))); customUI->verticalCheckbox->setChecked((bool)m_configGroup.readEntry("mirrorVertically", false)); // translate mode options customUI->translationRadiusSpinbox->setRange(0, 200); customUI->translationRadiusSpinbox->setSuffix(i18n(" px")); customUI->translationRadiusSpinbox->setValue(m_configGroup.readEntry("translateRadius", 0)); connect(customUI->translationRadiusSpinbox,SIGNAL(valueChanged(int)),this,SLOT(slotSetTranslateRadius(int))); // Copy translate mode options and actions connect(customUI->addSubbrushButton, &QPushButton::clicked, this, &KisToolMultihand::slotAddSubbrushesMode); connect(customUI->removeSubbrushButton, &QPushButton::clicked, this, &KisToolMultihand::slotRemoveAllSubbrushes); // snowflake re-uses the existing options, so there is no special parameters for that... return static_cast(customUI); // keeping it in the native class until the end allows us to access the UI components } void KisToolMultihand::activateAxesPointModeSetup() { if (customUI->moveOriginButton->isChecked()){ m_setupAxesFlag = true; useCursor(KisCursor::crossCursor()); updateCanvas(); } else { finishAxesSetup(); } } void KisToolMultihand::resetAxes() { m_axesPoint = QPointF(0.5 * image()->width(), 0.5 * image()->height()); finishAxesSetup(); } void KisToolMultihand::finishAxesSetup() { m_setupAxesFlag = false; customUI->moveOriginButton->setChecked(false); resetCursorStyle(); updateCanvas(); } void KisToolMultihand::updateCanvas() { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); Q_ASSERT(kisCanvas); kisCanvas->updateCanvas(); } void KisToolMultihand::slotSetHandsCount(int count) { m_handsCount = count; m_configGroup.writeEntry("handsCount", count); updateCanvas(); } void KisToolMultihand::slotSetAxesAngle(qreal angle) { //negative so axes rotates counter clockwise m_angle = -angle*M_PI/180; updateCanvas(); m_configGroup.writeEntry("axesAngle", angle); } void KisToolMultihand::slotSetTransformMode(int index) { m_transformMode = enumTransforModes(customUI->multihandTypeCombobox->itemData(index).toInt()); m_configGroup.writeEntry("transformMode", index); // hide all of the UI elements by default customUI->horizontalCheckbox->setVisible(false); customUI->verticalCheckbox->setVisible(false); customUI->translationRadiusSpinbox->setVisible(false); customUI->radiusLabel->setVisible(false); customUI->brushCountSpinBox->setVisible(false); customUI->brushesLabel->setVisible(false); customUI->subbrushLabel->setVisible(false); customUI->addSubbrushButton->setVisible(false); customUI->removeSubbrushButton->setVisible(false); + m_addSubbrushesMode = 0; // turn on what we need if (index == MIRROR) { customUI->horizontalCheckbox->setVisible(true); customUI->verticalCheckbox->setVisible(true); } else if (index == TRANSLATE) { customUI->translationRadiusSpinbox->setVisible(true); customUI->radiusLabel->setVisible(true); customUI->brushCountSpinBox->setVisible(true); customUI->brushesLabel->setVisible(true); } else if (index == SYMMETRY || index == SNOWFLAKE || index == TRANSLATE ) { customUI->brushCountSpinBox->setVisible(true); customUI->brushesLabel->setVisible(true); } else if (index == COPYTRANSLATE) { customUI->subbrushLabel->setVisible(true); customUI->addSubbrushButton->setVisible(true); + customUI->addSubbrushButton->setChecked(false); customUI->removeSubbrushButton->setVisible(true); } } void KisToolMultihand::slotSetAxesVisible(bool vis) { m_showAxes = vis; updateCanvas(); m_configGroup.writeEntry("showAxes", vis); } void KisToolMultihand::slotSetMirrorVertically(bool mirror) { m_mirrorVertically = mirror; updateCanvas(); m_configGroup.writeEntry("mirrorVertically", mirror); } void KisToolMultihand::slotSetMirrorHorizontally(bool mirror) { m_mirrorHorizontally = mirror; updateCanvas(); m_configGroup.writeEntry("mirrorHorizontally", mirror); } void KisToolMultihand::slotSetTranslateRadius(int radius) { m_translateRadius = radius; m_configGroup.writeEntry("translateRadius", radius); } void KisToolMultihand::slotAddSubbrushesMode(bool checked) { m_addSubbrushesMode = checked; updateCanvas(); } void KisToolMultihand::slotRemoveAllSubbrushes() { m_subbrOriginalLocations.clear(); updateCanvas(); } diff --git a/plugins/tools/defaulttool/defaulttool/ShapeGradientEditStrategy.cpp b/plugins/tools/defaulttool/defaulttool/ShapeGradientEditStrategy.cpp index 626032923b..63f0c755b4 100644 --- a/plugins/tools/defaulttool/defaulttool/ShapeGradientEditStrategy.cpp +++ b/plugins/tools/defaulttool/defaulttool/ShapeGradientEditStrategy.cpp @@ -1,111 +1,111 @@ /* * 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 "ShapeGradientEditStrategy.h" #include #include #include #include #include #include "kis_assert.h" #include "SelectionDecorator.h" #include #include #include #include "kis_debug.h" struct ShapeGradientEditStrategy::Private { Private(const QPointF &_start, KoShape *shape, KoFlake::FillVariant fillVariant) : start(_start), gradientHandles(fillVariant, shape) { } QPointF start; QPointF initialOffset; KoShapeGradientHandles gradientHandles; - KoShapeGradientHandles::Handle::Type handleType; + KoShapeGradientHandles::Handle::Type handleType {KoShapeGradientHandles::Handle::Type::None}; QScopedPointer intermediateCommand; }; ShapeGradientEditStrategy::ShapeGradientEditStrategy(KoToolBase *tool, KoFlake::FillVariant fillVariant, KoShape *shape, KoShapeGradientHandles::Handle::Type startHandleType, const QPointF &clicked) : KoInteractionStrategy(tool) , m_d(new Private(clicked, shape, fillVariant)) { KIS_SAFE_ASSERT_RECOVER_RETURN(shape); m_d->handleType = startHandleType; KoShapeGradientHandles::Handle handle = m_d->gradientHandles.getHandle(m_d->handleType); m_d->initialOffset = handle.pos - clicked; KisSnapPointStrategy *strategy = new KisSnapPointStrategy(); Q_FOREACH (const KoShapeGradientHandles::Handle &h, m_d->gradientHandles.handles()) { strategy->addPoint(h.pos); } tool->canvas()->snapGuide()->addCustomSnapStrategy(strategy); } ShapeGradientEditStrategy::~ShapeGradientEditStrategy() { } void ShapeGradientEditStrategy::handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers) { if (m_d->intermediateCommand) { m_d->intermediateCommand->undo(); m_d->intermediateCommand.reset(); } const QPointF snappedPosition = tool()->canvas()->snapGuide()->snap(mouseLocation, m_d->initialOffset, modifiers); const QPointF diff = snappedPosition- m_d->start; m_d->intermediateCommand.reset(m_d->gradientHandles.moveGradientHandle(m_d->handleType, diff)); m_d->intermediateCommand->redo(); tool()->canvas()->updateCanvas(tool()->canvas()->snapGuide()->boundingRect()); } KUndo2Command *ShapeGradientEditStrategy::createCommand() { return m_d->intermediateCommand.take(); } void ShapeGradientEditStrategy::finishInteraction(Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); const QRectF dirtyRect = tool()->canvas()->snapGuide()->boundingRect(); tool()->canvas()->snapGuide()->reset(); tool()->canvas()->updateCanvas(dirtyRect); } void ShapeGradientEditStrategy::paint(QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(painter); Q_UNUSED(converter); } diff --git a/plugins/tools/selectiontools/CMakeLists.txt b/plugins/tools/selectiontools/CMakeLists.txt index 756cab5959..8b9aff2c17 100644 --- a/plugins/tools/selectiontools/CMakeLists.txt +++ b/plugins/tools/selectiontools/CMakeLists.txt @@ -1,37 +1,35 @@ -add_subdirectory(tests) - set(kritaselectiontools_SOURCES selection_tools.cc kis_tool_select_rectangular.cc kis_tool_select_polygonal.cc kis_tool_select_elliptical.cc kis_tool_select_contiguous.cc kis_tool_select_outline.cc kis_tool_select_path.cc kis_tool_select_similar.cc kis_selection_modifier_mapper.cc KisMagneticWorker.cc KisToolSelectMagnetic.cc ) qt5_add_resources(kritaselectiontools_SOURCES selectiontools.qrc) add_library(kritaselectiontools MODULE ${kritaselectiontools_SOURCES}) generate_export_header(kritaselectiontools BASE_NAME kritaselectiontools) target_link_libraries(kritaselectiontools kritaui kritabasicflakes kritaimage) install(TARGETS kritaselectiontools DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( FILES KisToolSelectPolygonal.action KisToolSelectElliptical.action KisToolSelectSimilar.action KisToolSelectContiguous.action KisToolSelectRectangular.action KisToolSelectOutline.action KisToolSelectPath.action KisToolSelectMagnetic.action DESTINATION ${DATA_INSTALL_DIR}/krita/actions ) diff --git a/plugins/tools/selectiontools/tests/CMakeLists.txt b/plugins/tools/selectiontools/tests/CMakeLists.txt deleted file mode 100644 index 18ed773851..0000000000 --- a/plugins/tools/selectiontools/tests/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) -include_directories( - ${CMAKE_CURRENT_SOURCE_DIR}/.. - ${CMAKE_CURRENT_BINARY_DIR}/.. - ${CMAKE_SOURCE_DIR}/sdk/tests -) - -macro_add_unittest_definitions() - -########### next target ############### - -ecm_add_test(KisMagneticWorkerTest.cc - NAME_PREFIX plugins-magneticselection- - LINK_LIBRARIES kritaselectiontools kritaimage Qt5::Test) diff --git a/plugins/tools/selectiontools/tests/KisMagneticWorkerTest.cc b/plugins/tools/selectiontools/tests/KisMagneticWorkerTest.cc deleted file mode 100644 index 33c9006f66..0000000000 --- a/plugins/tools/selectiontools/tests/KisMagneticWorkerTest.cc +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2019 Kuntal Majumder - * - * This library is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; version 2.1 of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include "KisMagneticWorkerTest.h" - -#include -#include -#include -#include -#include - -#include - -inline KisPaintDeviceSP loadTestImage(const QString &name, bool convertToAlpha) -{ - QImage image(TestUtil::fetchDataFileLazy(name)); - KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); - dev->convertFromQImage(image, 0); - - if (convertToAlpha) { - dev = KisPainter::convertToAlphaAsAlpha(dev); - } - - return dev; -} - -void KisMagneticWorkerTest::testWorker() -{ - KisPaintDeviceSP dev = loadTestImage("test_main.png", false); - const QRect rect = dev->exactBounds(); - KisPaintDeviceSP grayscaleDev = KisPainter::convertToAlphaAsGray(dev); - KisMagneticWorker worker(grayscaleDev); - - const QPoint startPos(80, 10); - const QPoint endPos(10, 100); - - auto points = worker.computeEdge(20, startPos, endPos, 3.0); - KIS_DUMP_DEVICE_2(grayscaleDev, rect, "draw", "dd"); - - /* - QVector result = { QPointF(50,65), - QPointF(49,64), - QPointF(48,63), - QPointF(47,62), - QPointF(46,61), - QPointF(45,60), - QPointF(44,59), - QPointF(44,58), - QPointF(44,57), - QPointF(44,56), - QPointF(44,55), - QPointF(44,54), - QPointF(44,53), - QPointF(44,52), - QPointF(44,51), - QPointF(44,50), - QPointF(44,49), - QPointF(44,48), - QPointF(44,47), - QPointF(44,46), - QPointF(44,45), - QPointF(44,44), - QPointF(44,43), - QPointF(44,42), - QPointF(43,41), - QPointF(43,40), - QPointF(43,39), - QPointF(44,38), - QPointF(44,37), - QPointF(44,36), - QPointF(44,35), - QPointF(44,34), - QPointF(44,33), - QPointF(44,32), - QPointF(44,31), - QPointF(44,30), - QPointF(44,29), - QPointF(44,28), - QPointF(44,27), - QPointF(44,26), - QPointF(44,25), - QPointF(44,24), - QPointF(44,23), - QPointF(44,22), - QPointF(44,21), - QPointF(44,20), - QPointF(44,19), - QPointF(44,18), - QPointF(43,17), - QPointF(44,16), - QPointF(44,15), - QPointF(44,14), - QPointF(44,13), - QPointF(43,12), - QPointF(42,11), - QPointF(41,11), - QPointF(40,10)}; - - QCOMPARE(result, points); - */ - - QImage img = dev->convertToQImage(0, rect); - img = img.convertToFormat(QImage::Format_ARGB32); - QPainter gc(&img); - - QPainterPath path; - - for (int i = 0; i < points.size(); i++) { - if (i == 0) { - path.moveTo(points[i]); - } else { - path.lineTo(points[i]); - } - } - - gc.setPen(Qt::blue); - gc.drawPath(path); - - gc.setPen(Qt::green); - gc.drawEllipse(startPos, 3, 3); - gc.setPen(Qt::red); - gc.drawEllipse(endPos, 2, 2); - - img.save("result.png"); - -} - -QTEST_MAIN(KisMagneticWorkerTest) diff --git a/plugins/tools/selectiontools/tests/KisMagneticWorkerTest.h b/plugins/tools/selectiontools/tests/KisMagneticWorkerTest.h deleted file mode 100644 index 934b6f8a4f..0000000000 --- a/plugins/tools/selectiontools/tests/KisMagneticWorkerTest.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2019 Kuntal Majumder - * - * This library is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; version 2.1 of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef KISMAGNETICWORKERTEST_H -#define KISMAGNETICWORKERTEST_H - -#include - -class KisMagneticWorkerTest : public QObject -{ - Q_OBJECT -private Q_SLOTS: - void testWorker(); - -}; - -#endif diff --git a/plugins/tools/selectiontools/tests/data/test_main.png b/plugins/tools/selectiontools/tests/data/test_main.png deleted file mode 100644 index cb0e8e5429..0000000000 Binary files a/plugins/tools/selectiontools/tests/data/test_main.png and /dev/null differ diff --git a/plugins/tools/svgtexttool/SvgTextEditor.cpp b/plugins/tools/svgtexttool/SvgTextEditor.cpp index c68ceb2010..790ec33544 100644 --- a/plugins/tools/svgtexttool/SvgTextEditor.cpp +++ b/plugins/tools/svgtexttool/SvgTextEditor.cpp @@ -1,1225 +1,1251 @@ /* This file is part of the KDE project * * Copyright 2017 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SvgTextEditor.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 #include "kis_font_family_combo_box.h" #include "FontSizeAction.h" #include "kis_signals_blocker.h" SvgTextEditor::SvgTextEditor(QWidget *parent, Qt::WindowFlags flags) : KXmlGuiWindow(parent, flags) , m_page(new QWidget(this)) #ifndef Q_OS_WIN , m_charSelectDialog(new KoDialog(this)) #endif { m_textEditorWidget.setupUi(m_page); setCentralWidget(m_page); m_textEditorWidget.chkVertical->setVisible(false); #ifndef Q_OS_WIN KCharSelect *charSelector = new KCharSelect(m_charSelectDialog, 0, KCharSelect::AllGuiElements); m_charSelectDialog->setMainWidget(charSelector); connect(charSelector, SIGNAL(currentCharChanged(QChar)), SLOT(insertCharacter(QChar))); m_charSelectDialog->hide(); m_charSelectDialog->setButtons(KoDialog::Close); #endif connect(m_textEditorWidget.buttons, SIGNAL(accepted()), this, SLOT(save())); connect(m_textEditorWidget.buttons, SIGNAL(rejected()), this, SLOT(slotCloseEditor())); connect(m_textEditorWidget.buttons, SIGNAL(clicked(QAbstractButton*)), this, SLOT(dialogButtonClicked(QAbstractButton*))); KConfigGroup cg(KSharedConfig::openConfig(), "SvgTextTool"); actionCollection()->setConfigGroup("SvgTextTool"); actionCollection()->setComponentName("svgtexttool"); actionCollection()->setComponentDisplayName(i18n("Text Tool")); - QByteArray state; if (cg.hasKey("WindowState")) { - state = cg.readEntry("State", state); - state = QByteArray::fromBase64(state); + QByteArray state = cg.readEntry("State", state); // One day will need to load the version number, but for now, assume 0 - restoreState(state); + restoreState(QByteArray::fromBase64(state)); + } + if (cg.hasKey("Geometry")) { + QByteArray ba = cg.readEntry("Geometry", QByteArray()); + restoreGeometry(QByteArray::fromBase64(ba)); + } + else { + 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; + const int deskWidth = desk.width(); + w = (deskWidth / 3) * 2; + h = (desk.height() / 3) * 2; + x += (desk.width() - w) / 2; + y += (desk.height() - h) / 2; + + move(x,y); + setGeometry(geometry().x(), geometry().y(), w, h); + } setAcceptDrops(true); //setStandardToolBarMenuEnabled(true); #ifdef Q_OS_MACOS setUnifiedTitleAndToolBarOnMac(true); #endif setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); m_syntaxHighlighter = new BasicXMLSyntaxHighlighter(m_textEditorWidget.svgTextEdit); m_textEditorWidget.svgTextEdit->setFont(QFontDatabase().systemFont(QFontDatabase::FixedFont)); createActions(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "svgtexttool.xmlgui")); setXMLFile(":/kxmlgui5/svgtexttool.xmlgui"); guiFactory()->addClient(this); // 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->setToolButtonStyle(Qt::ToolButtonIconOnly); 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); } } plugActionList("toolbarlist", toolbarList); connect(m_textEditorWidget.textTab, SIGNAL(currentChanged(int)), this, SLOT(switchTextEditorTab())); switchTextEditorTab(); m_textEditorWidget.richTextEdit->document()->setDefaultStyleSheet("p {margin:0px;}"); applySettings(); + } SvgTextEditor::~SvgTextEditor() { KConfigGroup g(KSharedConfig::openConfig(), "SvgTextTool"); QByteArray ba = saveState(); g.writeEntry("windowState", ba.toBase64()); + ba = saveGeometry(); + g.writeEntry("Geometry", ba.toBase64()); } void SvgTextEditor::setShape(KoSvgTextShape *shape) { m_shape = shape; if (m_shape) { KoSvgTextShapeMarkupConverter converter(m_shape); QString svg; QString styles; QTextDocument *doc = m_textEditorWidget.richTextEdit->document(); if (converter.convertToSvg(&svg, &styles)) { m_textEditorWidget.svgTextEdit->setPlainText(svg); m_textEditorWidget.svgStylesEdit->setPlainText(styles); m_textEditorWidget.svgTextEdit->document()->setModified(false); if (shape->isRichTextPreferred() && converter.convertSvgToDocument(svg, doc)) { m_textEditorWidget.richTextEdit->setDocument(doc); KisSignalsBlocker b(m_textEditorWidget.textTab); m_textEditorWidget.textTab->setCurrentIndex(Richtext); doc->clearUndoRedoStacks(); switchTextEditorTab(false); } else { KisSignalsBlocker b(m_textEditorWidget.textTab); m_textEditorWidget.textTab->setCurrentIndex(SvgSource); switchTextEditorTab(false); } } else { QMessageBox::warning(this, i18n("Conversion failed"), "Could not get svg text from the shape:\n" + converter.errors().join('\n') + "\n" + converter.warnings().join('\n')); } } KisFontComboBoxes* fontComboBox = qobject_cast(qobject_cast(actionCollection()->action("svg_font"))->defaultWidget()); fontComboBox->setInitialized(); } void SvgTextEditor::save() { if (m_shape) { if (m_textEditorWidget.textTab->currentIndex() == Richtext) { QString svg; QString styles = m_textEditorWidget.svgStylesEdit->document()->toPlainText(); KoSvgTextShapeMarkupConverter converter(m_shape); if (!converter.convertDocumentToSvg(m_textEditorWidget.richTextEdit->document(), &svg)) { qWarning()<<"new converter doesn't work!"; } m_textEditorWidget.richTextEdit->document()->setModified(false); emit textUpdated(m_shape, svg, styles, true); } else { emit textUpdated(m_shape, m_textEditorWidget.svgTextEdit->document()->toPlainText(), m_textEditorWidget.svgStylesEdit->document()->toPlainText(), false); m_textEditorWidget.svgTextEdit->document()->setModified(false); } } } void SvgTextEditor::switchTextEditorTab(bool convertData) { KoSvgTextShape shape; KoSvgTextShapeMarkupConverter converter(&shape); if (m_currentEditor) { disconnect(m_currentEditor->document(), SIGNAL(modificationChanged(bool)), this, SLOT(setModified(bool))); } if (m_textEditorWidget.textTab->currentIndex() == Richtext) { //first, make buttons checkable enableRichTextActions(true); enableSvgTextActions(false); //then connect the cursor change to the checkformat(); connect(m_textEditorWidget.richTextEdit, SIGNAL(cursorPositionChanged()), this, SLOT(checkFormat())); connect(m_textEditorWidget.richTextEdit, SIGNAL(textChanged()), this, SLOT(slotFixUpEmptyTextBlock())); checkFormat(); if (m_shape && convertData) { QTextDocument *doc = m_textEditorWidget.richTextEdit->document(); if (!converter.convertSvgToDocument(m_textEditorWidget.svgTextEdit->document()->toPlainText(), doc)) { qWarning()<<"new converter svgToDoc doesn't work!"; } m_textEditorWidget.richTextEdit->setDocument(doc); doc->clearUndoRedoStacks(); } m_currentEditor = m_textEditorWidget.richTextEdit; } else { //first, make buttons uncheckable enableRichTextActions(false); enableSvgTextActions(true); disconnect(m_textEditorWidget.richTextEdit, SIGNAL(cursorPositionChanged()), this, SLOT(checkFormat())); // Convert the rich text to svg and styles strings if (m_shape && convertData) { QString svg; QString styles; if (!converter.convertDocumentToSvg(m_textEditorWidget.richTextEdit->document(), &svg)) { qWarning()<<"new converter docToSVG doesn't work!"; } m_textEditorWidget.svgTextEdit->setPlainText(svg); } m_currentEditor = m_textEditorWidget.svgTextEdit; } connect(m_currentEditor->document(), SIGNAL(modificationChanged(bool)), SLOT(setModified(bool))); } void SvgTextEditor::checkFormat() { QTextCharFormat format = m_textEditorWidget.richTextEdit->textCursor().charFormat(); QTextBlockFormat blockFormat = m_textEditorWidget.richTextEdit->textCursor().blockFormat(); // checkboxes do not emit signals on manual switching, so we // can avoid blocking them if (format.fontWeight() > QFont::Normal) { actionCollection()->action("svg_weight_bold")->setChecked(true); } else { actionCollection()->action("svg_weight_bold")->setChecked(false); } actionCollection()->action("svg_format_italic")->setChecked(format.fontItalic()); actionCollection()->action("svg_format_underline")->setChecked(format.fontUnderline()); actionCollection()->action("svg_format_strike_through")->setChecked(format.fontStrikeOut()); { FontSizeAction *fontSizeAction = qobject_cast(actionCollection()->action("svg_font_size")); KisSignalsBlocker b(fontSizeAction); fontSizeAction->setFontSize(format.font().pointSize()); } { KoColor fg(format.foreground().color(), KoColorSpaceRegistry::instance()->rgb8()); KoColorPopupAction *fgColorPopup = qobject_cast(actionCollection()->action("svg_format_textcolor")); KisSignalsBlocker b(fgColorPopup); fgColorPopup->setCurrentColor(fg); } { KoColor bg(format.foreground().color(), KoColorSpaceRegistry::instance()->rgb8()); KoColorPopupAction *bgColorPopup = qobject_cast(actionCollection()->action("svg_background_color")); KisSignalsBlocker b(bgColorPopup); bgColorPopup->setCurrentColor(bg); } { KisFontComboBoxes* fontComboBox = qobject_cast(qobject_cast(actionCollection()->action("svg_font"))->defaultWidget()); KisSignalsBlocker b(fontComboBox); fontComboBox->setCurrentFont(format.font()); } { QDoubleSpinBox *spnLineHeight = qobject_cast(qobject_cast(actionCollection()->action("svg_line_height"))->defaultWidget()); KisSignalsBlocker b(spnLineHeight); if (blockFormat.lineHeightType() == QTextBlockFormat::SingleHeight) { spnLineHeight->setValue(100.0); } else if(blockFormat.lineHeightType() == QTextBlockFormat::ProportionalHeight) { spnLineHeight->setValue(double(blockFormat.lineHeight())); } } { QDoubleSpinBox* spnLetterSpacing = qobject_cast(qobject_cast(actionCollection()->action("svg_letter_spacing"))->defaultWidget()); KisSignalsBlocker b(spnLetterSpacing); spnLetterSpacing->setValue(format.fontLetterSpacing()); } } void SvgTextEditor::slotFixUpEmptyTextBlock() { if (m_textEditorWidget.richTextEdit->document()->isEmpty()) { QTextCursor cursor = m_textEditorWidget.richTextEdit->textCursor(); QTextCharFormat format = cursor.blockCharFormat(); { FontSizeAction *fontSizeAction = qobject_cast(actionCollection()->action("svg_font_size")); KisFontComboBoxes* fontComboBox = qobject_cast(qobject_cast(actionCollection()->action("svg_font"))->defaultWidget()); format.setFont(fontComboBox->currentFont(fontSizeAction->fontSize())); } { KoColorPopupAction *fgColorPopup = qobject_cast(actionCollection()->action("svg_format_textcolor")); format.setForeground(fgColorPopup->currentColor()); } { //KoColorPopupAction *bgColorPopup = qobject_cast(actionCollection()->action("svg_background_color")); //format.setBackground(bgColorPopup->currentColor()); } KisSignalsBlocker b(m_textEditorWidget.richTextEdit); cursor.setBlockCharFormat(format); } } void SvgTextEditor::undo() { m_currentEditor->undo(); } void SvgTextEditor::redo() { m_currentEditor->redo(); } void SvgTextEditor::cut() { m_currentEditor->cut(); } void SvgTextEditor::copy() { m_currentEditor->copy(); } void SvgTextEditor::paste() { m_currentEditor->paste(); } void SvgTextEditor::selectAll() { m_currentEditor->selectAll(); } void SvgTextEditor::deselect() { QTextCursor cursor(m_currentEditor->textCursor()); cursor.clearSelection(); m_currentEditor->setTextCursor(cursor); } void SvgTextEditor::find() { QDialog *findDialog = new QDialog(this); findDialog->setWindowTitle(i18n("Find Text")); QFormLayout *layout = new QFormLayout(); findDialog->setLayout(layout); QLineEdit *lnSearchKey = new QLineEdit(); layout->addRow(i18n("Find:"), lnSearchKey); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); findDialog->layout()->addWidget(buttons); connect(buttons, SIGNAL(accepted()), findDialog, SLOT(accept())); connect(buttons, SIGNAL(rejected()), findDialog, SLOT(reject())); if (findDialog->exec()==QDialog::Accepted) { m_searchKey = lnSearchKey->text(); m_currentEditor->find(m_searchKey); } } void SvgTextEditor::findNext() { if (!m_currentEditor->find(m_searchKey)) { QTextCursor cursor(m_currentEditor->textCursor()); cursor.movePosition(QTextCursor::Start); m_currentEditor->setTextCursor(cursor); m_currentEditor->find(m_searchKey); } } void SvgTextEditor::findPrev() { if (!m_currentEditor->find(m_searchKey,QTextDocument::FindBackward)) { QTextCursor cursor(m_currentEditor->textCursor()); cursor.movePosition(QTextCursor::End); m_currentEditor->setTextCursor(cursor); m_currentEditor->find(m_searchKey,QTextDocument::FindBackward); } } void SvgTextEditor::replace() { QDialog *findDialog = new QDialog(this); findDialog->setWindowTitle(i18n("Find and Replace all")); QFormLayout *layout = new QFormLayout(); findDialog->setLayout(layout); QLineEdit *lnSearchKey = new QLineEdit(); QLineEdit *lnReplaceKey = new QLineEdit(); layout->addRow(i18n("Find:"), lnSearchKey); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); layout->addRow(i18n("Replace:"), lnReplaceKey); findDialog->layout()->addWidget(buttons); connect(buttons, SIGNAL(accepted()), findDialog, SLOT(accept())); connect(buttons, SIGNAL(rejected()), findDialog, SLOT(reject())); if (findDialog->exec()==QDialog::Accepted) { QString search = lnSearchKey->text(); QString replace = lnReplaceKey->text(); QTextCursor cursor(m_currentEditor->textCursor()); cursor.movePosition(QTextCursor::Start); m_currentEditor->setTextCursor(cursor); while(m_currentEditor->find(search)) { m_currentEditor->textCursor().removeSelectedText(); m_currentEditor->textCursor().insertText(replace); } } } void SvgTextEditor::zoomOut() { m_currentEditor->zoomOut(); } void SvgTextEditor::zoomIn() { m_currentEditor->zoomIn(); } #ifndef Q_OS_WIN void SvgTextEditor::showInsertSpecialCharacterDialog() { m_charSelectDialog->setVisible(!m_charSelectDialog->isVisible()); } void SvgTextEditor::insertCharacter(const QChar &c) { m_currentEditor->textCursor().insertText(QString(c)); } #endif void SvgTextEditor::setTextBold(QFont::Weight weight) { if (m_textEditorWidget.textTab->currentIndex() == Richtext) { QTextCharFormat format; QTextCursor oldCursor = setTextSelection(); if (m_textEditorWidget.richTextEdit->textCursor().charFormat().fontWeight() > QFont::Normal && weight==QFont::Bold) { format.setFontWeight(QFont::Normal); } else { format.setFontWeight(weight); } m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } else { QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor(); if (cursor.hasSelection()) { QString selectionModified = "" + cursor.selectedText() + ""; cursor.removeSelectedText(); cursor.insertText(selectionModified); } } } void SvgTextEditor::setTextWeightLight() { if (m_textEditorWidget.richTextEdit->textCursor().charFormat().fontWeight() < QFont::Normal) { setTextBold(QFont::Normal); } else { setTextBold(QFont::Light); } } void SvgTextEditor::setTextWeightNormal() { setTextBold(QFont::Normal); } void SvgTextEditor::setTextWeightDemi() { if (m_textEditorWidget.richTextEdit->textCursor().charFormat().fontWeight() != QFont::Normal) { setTextBold(QFont::Normal); } else { setTextBold(QFont::DemiBold); } } void SvgTextEditor::setTextWeightBlack() { if (m_textEditorWidget.richTextEdit->textCursor().charFormat().fontWeight()>QFont::Normal) { setTextBold(QFont::Normal); } else { setTextBold(QFont::Black); } } void SvgTextEditor::setTextItalic(QFont::Style style) { QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor(); QString fontStyle = "inherit"; if (style == QFont::StyleItalic) { fontStyle = "italic"; } else if(style == QFont::StyleOblique) { fontStyle = "oblique"; } if (m_textEditorWidget.textTab->currentIndex() == Richtext) { QTextCharFormat format; QTextCursor origCursor = setTextSelection(); format.setFontItalic(!m_textEditorWidget.richTextEdit->textCursor().charFormat().fontItalic()); m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(origCursor); } else { if (cursor.hasSelection()) { QString selectionModified = "" + cursor.selectedText() + ""; cursor.removeSelectedText(); cursor.insertText(selectionModified); } } } void SvgTextEditor::setTextDecoration(KoSvgText::TextDecoration decor) { QTextCursor cursor = setTextSelection(); QTextCharFormat currentFormat = m_textEditorWidget.richTextEdit->textCursor().charFormat(); QTextCharFormat format; QString textDecoration = "inherit"; if (decor == KoSvgText::DecorationUnderline) { textDecoration = "underline"; if (currentFormat.fontUnderline()) { format.setFontUnderline(false); } else { format.setFontUnderline(true); } format.setFontOverline(false); format.setFontStrikeOut(false); } else if (decor == KoSvgText::DecorationLineThrough) { textDecoration = "line-through"; format.setFontUnderline(false); format.setFontOverline(false); if (currentFormat.fontStrikeOut()) { format.setFontStrikeOut(false); } else { format.setFontStrikeOut(true); } } else if (decor == KoSvgText::DecorationOverline) { textDecoration = "overline"; format.setFontUnderline(false); if (currentFormat.fontOverline()) { format.setFontOverline(false); } else { format.setFontOverline(true); } format.setFontStrikeOut(false); } if (m_textEditorWidget.textTab->currentIndex() == Richtext) { m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); } else { if (cursor.hasSelection()) { QString selectionModified = "" + cursor.selectedText() + ""; cursor.removeSelectedText(); cursor.insertText(selectionModified); } } m_textEditorWidget.richTextEdit->setTextCursor(cursor); } void SvgTextEditor::setTextUnderline() { setTextDecoration(KoSvgText::DecorationUnderline); } void SvgTextEditor::setTextOverline() { setTextDecoration(KoSvgText::DecorationOverline); } void SvgTextEditor::setTextStrikethrough() { setTextDecoration(KoSvgText::DecorationLineThrough); } void SvgTextEditor::setTextSubscript() { QTextCharFormat format = m_textEditorWidget.richTextEdit->textCursor().charFormat(); if (format.verticalAlignment()==QTextCharFormat::AlignSubScript) { format.setVerticalAlignment(QTextCharFormat::AlignNormal); } else { format.setVerticalAlignment(QTextCharFormat::AlignSubScript); } m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); } void SvgTextEditor::setTextSuperScript() { QTextCharFormat format = m_textEditorWidget.richTextEdit->textCursor().charFormat(); if (format.verticalAlignment()==QTextCharFormat::AlignSuperScript) { format.setVerticalAlignment(QTextCharFormat::AlignNormal); } else { format.setVerticalAlignment(QTextCharFormat::AlignSuperScript); } m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); } void SvgTextEditor::increaseTextSize() { QTextCursor oldCursor = setTextSelection(); QTextCharFormat format; int pointSize = m_textEditorWidget.richTextEdit->textCursor().charFormat().font().pointSize(); if (pointSize<0) { pointSize = m_textEditorWidget.richTextEdit->textCursor().charFormat().font().pixelSize(); } format.setFontPointSize(pointSize+1.0); m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } void SvgTextEditor::decreaseTextSize() { QTextCursor oldCursor = setTextSelection(); QTextCharFormat format; int pointSize = m_textEditorWidget.richTextEdit->textCursor().charFormat().font().pointSize(); if (pointSize<1) { pointSize = m_textEditorWidget.richTextEdit->textCursor().charFormat().font().pixelSize(); } format.setFontPointSize(qMax(pointSize-1.0, 1.0)); m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } void SvgTextEditor::setLineHeight(double lineHeightPercentage) { QTextCursor oldCursor = setTextSelection(); QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat(); format.setLineHeight(lineHeightPercentage, QTextBlockFormat::ProportionalHeight); m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } void SvgTextEditor::setLetterSpacing(double letterSpacing) { QTextCursor cursor = setTextSelection(); if (m_textEditorWidget.textTab->currentIndex() == Richtext) { QTextCharFormat format; format.setFontLetterSpacingType(QFont::AbsoluteSpacing); format.setFontLetterSpacing(letterSpacing); m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(cursor); } else { if (cursor.hasSelection()) { QString selectionModified = "" + cursor.selectedText() + ""; cursor.removeSelectedText(); cursor.insertText(selectionModified); } } } void SvgTextEditor::alignLeft() { QTextCursor oldCursor = setTextSelection(); QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat(); format.setAlignment(Qt::AlignLeft); m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } void SvgTextEditor::alignRight() { QTextCursor oldCursor = setTextSelection(); QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat(); format.setAlignment(Qt::AlignRight); m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } void SvgTextEditor::alignCenter() { QTextCursor oldCursor = setTextSelection(); QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat(); format.setAlignment(Qt::AlignCenter); m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } void SvgTextEditor::alignJustified() { QTextCursor oldCursor = setTextSelection(); QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat(); format.setAlignment(Qt::AlignJustify); m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } void SvgTextEditor::setSettings() { KoDialog settingsDialog(this); Ui_WdgSvgTextSettings textSettings; QWidget *settingsPage = new QWidget(&settingsDialog, 0); settingsDialog.setMainWidget(settingsPage); textSettings.setupUi(settingsPage); // get the settings and initialize the dialog KConfigGroup cfg(KSharedConfig::openConfig(), "SvgTextTool"); QStringList selectedWritingSystems = cfg.readEntry("selectedWritingSystems", "").split(","); QList scripts = QFontDatabase().writingSystems(); QStandardItemModel *writingSystemsModel = new QStandardItemModel(&settingsDialog); for (int s = 0; s < scripts.size(); s ++) { QString writingSystem = QFontDatabase().writingSystemName(scripts.at(s)); QStandardItem *script = new QStandardItem(writingSystem); script->setCheckable(true); script->setCheckState(selectedWritingSystems.contains(QString::number(scripts.at(s))) ? Qt::Checked : Qt::Unchecked); script->setData((int)scripts.at(s)); writingSystemsModel->appendRow(script); } textSettings.lwScripts->setModel(writingSystemsModel); EditorMode mode = (EditorMode)cfg.readEntry("EditorMode", (int)Both); switch(mode) { case(RichText): textSettings.radioRichText->setChecked(true); break; case(SvgSource): textSettings.radioSvgSource->setChecked(true); break; case(Both): textSettings.radioBoth->setChecked(true); } QColor background = cfg.readEntry("colorEditorBackground", qApp->palette().window().color()); textSettings.colorEditorBackground->setColor(background); textSettings.colorEditorForeground->setColor(cfg.readEntry("colorEditorForeground", qApp->palette().text().color())); textSettings.colorKeyword->setColor(cfg.readEntry("colorKeyword", QColor(background.value() < 100 ? Qt::cyan : Qt::blue))); textSettings.chkBoldKeyword->setChecked(cfg.readEntry("BoldKeyword", true)); textSettings.chkItalicKeyword->setChecked(cfg.readEntry("ItalicKeyword", false)); textSettings.colorElement->setColor(cfg.readEntry("colorElement", QColor(background.value() < 100 ? Qt::magenta : Qt::darkMagenta))); textSettings.chkBoldElement->setChecked(cfg.readEntry("BoldElement", true)); textSettings.chkItalicElement->setChecked(cfg.readEntry("ItalicElement", false)); textSettings.colorAttribute->setColor(cfg.readEntry("colorAttribute", QColor(background.value() < 100 ? Qt::green : Qt::darkGreen))); textSettings.chkBoldAttribute->setChecked(cfg.readEntry("BoldAttribute", true)); textSettings.chkItalicAttribute->setChecked(cfg.readEntry("ItalicAttribute", true)); textSettings.colorValue->setColor(cfg.readEntry("colorValue", QColor(background.value() < 100 ? Qt::red: Qt::darkRed))); textSettings.chkBoldValue->setChecked(cfg.readEntry("BoldValue", true)); textSettings.chkItalicValue->setChecked(cfg.readEntry("ItalicValue", false)); textSettings.colorComment->setColor(cfg.readEntry("colorComment", QColor(background.value() < 100 ? Qt::lightGray : Qt::gray))); textSettings.chkBoldComment->setChecked(cfg.readEntry("BoldComment", false)); textSettings.chkItalicComment->setChecked(cfg.readEntry("ItalicComment", false)); settingsDialog.setButtons(KoDialog::Ok | KoDialog::Cancel); if (settingsDialog.exec() == QDialog::Accepted) { // save and set the settings QStringList writingSystems; for (int i = 0; i < writingSystemsModel->rowCount(); i++) { QStandardItem *item = writingSystemsModel->item(i); if (item->checkState() == Qt::Checked) { writingSystems.append(QString::number(item->data().toInt())); } } cfg.writeEntry("selectedWritingSystems", writingSystems.join(',')); if (textSettings.radioRichText->isChecked()) { cfg.writeEntry("EditorMode", (int)Richtext); } else if (textSettings.radioSvgSource->isChecked()) { cfg.writeEntry("EditorMode", (int)SvgSource); } else if (textSettings.radioBoth->isChecked()) { cfg.writeEntry("EditorMode", (int)Both); } cfg.writeEntry("colorEditorBackground", textSettings.colorEditorBackground->color()); cfg.writeEntry("colorEditorForeground", textSettings.colorEditorForeground->color()); cfg.writeEntry("colorKeyword", textSettings.colorKeyword->color()); cfg.writeEntry("BoldKeyword", textSettings.chkBoldKeyword->isChecked()); cfg.writeEntry("ItalicKeyWord", textSettings.chkItalicKeyword->isChecked()); cfg.writeEntry("colorElement", textSettings.colorElement->color()); cfg.writeEntry("BoldElement", textSettings.chkBoldElement->isChecked()); cfg.writeEntry("ItalicElement", textSettings.chkItalicElement->isChecked()); cfg.writeEntry("colorAttribute", textSettings.colorAttribute->color()); cfg.writeEntry("BoldAttribute", textSettings.chkBoldAttribute->isChecked()); cfg.writeEntry("ItalicAttribute", textSettings.chkItalicAttribute->isChecked()); cfg.writeEntry("colorValue", textSettings.colorValue->color()); cfg.writeEntry("BoldValue", textSettings.chkBoldValue->isChecked()); cfg.writeEntry("ItalicValue", textSettings.chkItalicValue->isChecked()); cfg.writeEntry("colorComment", textSettings.colorComment->color()); cfg.writeEntry("BoldComment", textSettings.chkBoldComment->isChecked()); cfg.writeEntry("ItalicComment", textSettings.chkItalicComment->isChecked()); applySettings(); } } void SvgTextEditor::slotToolbarToggled(bool) { } void SvgTextEditor::setFontColor(const KoColor &c) { QColor color = c.toQColor(); if (m_textEditorWidget.textTab->currentIndex() == Richtext) { QTextCursor oldCursor = setTextSelection(); QTextCharFormat format; format.setForeground(QBrush(color)); m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } else { QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor(); if (cursor.hasSelection()) { QString selectionModified = "" + cursor.selectedText() + ""; cursor.removeSelectedText(); cursor.insertText(selectionModified); } } } void SvgTextEditor::setBackgroundColor(const KoColor &c) { QColor color = c.toQColor(); QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor(); if (cursor.hasSelection()) { QString selectionModified = "" + cursor.selectedText() + ""; cursor.removeSelectedText(); cursor.insertText(selectionModified); } } void SvgTextEditor::setModified(bool modified) { if (modified) { m_textEditorWidget.buttons->setStandardButtons(QDialogButtonBox::Save | QDialogButtonBox::Discard); } else { m_textEditorWidget.buttons->setStandardButtons(QDialogButtonBox::Save | QDialogButtonBox::Close); } } void SvgTextEditor::dialogButtonClicked(QAbstractButton *button) { if (m_textEditorWidget.buttons->standardButton(button) == QDialogButtonBox::Discard) { if (QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("You have modified the text. Discard changes?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { close(); } } } void SvgTextEditor::setFont(const QString &fontName) { QFont font; font.fromString(fontName); QTextCharFormat curFormat = m_textEditorWidget.richTextEdit->textCursor().charFormat(); font.setPointSize(curFormat.font().pointSize()); QTextCharFormat format; //This disables the style being set from the font-comboboxes too, so we need to rethink how we use that. format.setFontFamily(font.family()); if (m_textEditorWidget.textTab->currentIndex() == Richtext) { QTextCursor oldCursor = setTextSelection(); m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } else { QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor(); if (cursor.hasSelection()) { QString selectionModified = "" + cursor.selectedText() + ""; cursor.removeSelectedText(); cursor.insertText(selectionModified); } } } void SvgTextEditor::setFontSize(qreal fontSize) { if (m_textEditorWidget.textTab->currentIndex() == Richtext) { QTextCursor oldCursor = setTextSelection(); QTextCharFormat format; format.setFontPointSize(fontSize); m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } else { QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor(); if (cursor.hasSelection()) { QString selectionModified = "" + cursor.selectedText() + ""; cursor.removeSelectedText(); cursor.insertText(selectionModified); } } } void SvgTextEditor::setBaseline(KoSvgText::BaselineShiftMode) { QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor(); if (cursor.hasSelection()) { QString selectionModified = "" + cursor.selectedText() + ""; cursor.removeSelectedText(); cursor.insertText(selectionModified); } } void SvgTextEditor::wheelEvent(QWheelEvent *event) { if (event->modifiers() & Qt::ControlModifier) { int numDegrees = event->delta() / 8; int numSteps = numDegrees / 7; m_textEditorWidget.svgTextEdit->zoomOut(numSteps); event->accept(); } } QTextCursor SvgTextEditor::setTextSelection() { QTextCursor orignalCursor(m_textEditorWidget.richTextEdit->textCursor()); if (!orignalCursor.hasSelection()){ m_textEditorWidget.richTextEdit->selectAll(); } return orignalCursor; } void SvgTextEditor::applySettings() { KConfigGroup cfg(KSharedConfig::openConfig(), "SvgTextTool"); EditorMode mode = (EditorMode)cfg.readEntry("EditorMode", (int)Both); QWidget *richTab = m_textEditorWidget.richTab; QWidget *svgTab = m_textEditorWidget.svgTab; m_page->setUpdatesEnabled(false); m_textEditorWidget.textTab->clear(); switch(mode) { case(RichText): m_textEditorWidget.textTab->addTab(richTab, i18n("Rich text")); break; case(SvgSource): m_textEditorWidget.textTab->addTab(svgTab, i18n("SVG Source")); break; case(Both): m_textEditorWidget.textTab->addTab(richTab, i18n("Rich text")); m_textEditorWidget.textTab->addTab(svgTab, i18n("SVG Source")); } m_syntaxHighlighter->setFormats(); QPalette palette = m_textEditorWidget.svgTextEdit->palette(); QColor background = cfg.readEntry("colorEditorBackground", qApp->palette().window().color()); palette.setBrush(QPalette::Active, QPalette::Background, QBrush(background)); m_textEditorWidget.richTextEdit->setStyleSheet(QString("background-color:%1").arg(background.name())); m_textEditorWidget.svgStylesEdit->setStyleSheet(QString("background-color:%1").arg(background.name())); m_textEditorWidget.svgTextEdit->setStyleSheet(QString("background-color:%1").arg(background.name())); QColor foreground = cfg.readEntry("colorEditorForeground", qApp->palette().text().color()); palette.setBrush(QPalette::Active, QPalette::Text, QBrush(foreground)); QStringList selectedWritingSystems = cfg.readEntry("selectedWritingSystems", "").split(","); QVector writingSystems; for (int i=0; i(actionCollection()->action("svg_font_size")); KisFontComboBoxes* fontComboBox = qobject_cast(qobject_cast(actionCollection()->action("svg_font"))->defaultWidget()); const QFont oldFont = fontComboBox->currentFont(fontSizeAction->fontSize()); fontComboBox->refillComboBox(writingSystems); fontComboBox->setCurrentFont(oldFont); } m_page->setUpdatesEnabled(true); } QAction *SvgTextEditor::createAction(const QString &name, const char *member) { QAction *action = new QAction(this); KisActionRegistry *actionRegistry = KisActionRegistry::instance(); actionRegistry->propertizeAction(name, action); actionCollection()->addAction(name, action); QObject::connect(action, SIGNAL(triggered(bool)), this, member); return action; } void SvgTextEditor::createActions() { KisActionRegistry *actionRegistry = KisActionRegistry::instance(); // File: new, open, save, save as, close KStandardAction::save(this, SLOT(save()), actionCollection()); KStandardAction::close(this, SLOT(slotCloseEditor()), actionCollection()); // Edit KStandardAction::undo(this, SLOT(undo()), actionCollection()); KStandardAction::redo(this, SLOT(redo()), actionCollection()); KStandardAction::cut(this, SLOT(cut()), actionCollection()); KStandardAction::copy(this, SLOT(copy()), actionCollection()); KStandardAction::paste(this, SLOT(paste()), actionCollection()); KStandardAction::selectAll(this, SLOT(selectAll()), actionCollection()); KStandardAction::deselect(this, SLOT(deselect()), actionCollection()); KStandardAction::find(this, SLOT(find()), actionCollection()); KStandardAction::findNext(this, SLOT(findNext()), actionCollection()); KStandardAction::findPrev(this, SLOT(findPrev()), actionCollection()); KStandardAction::replace(this, SLOT(replace()), actionCollection()); // View // WISH: we cannot zoom-in/out in rech-text mode m_svgTextActions << KStandardAction::zoomOut(this, SLOT(zoomOut()), actionCollection()); m_svgTextActions << KStandardAction::zoomIn(this, SLOT(zoomIn()), actionCollection()); #ifndef Q_OS_WIN // Insert: QAction * insertAction = createAction("svg_insert_special_character", SLOT(showInsertSpecialCharacterDialog())); insertAction->setCheckable(true); insertAction->setChecked(false); #endif // Format: m_richTextActions << createAction("svg_weight_bold", SLOT(setTextBold())); m_richTextActions << createAction("svg_format_italic", SLOT(setTextItalic())); m_richTextActions << createAction("svg_format_underline", SLOT(setTextUnderline())); m_richTextActions << createAction("svg_format_strike_through", SLOT(setTextStrikethrough())); m_richTextActions << createAction("svg_format_superscript", SLOT(setTextSuperScript())); m_richTextActions << createAction("svg_format_subscript", SLOT(setTextSubscript())); m_richTextActions << createAction("svg_weight_light", SLOT(setTextWeightLight())); m_richTextActions << createAction("svg_weight_normal", SLOT(setTextWeightNormal())); m_richTextActions << createAction("svg_weight_demi", SLOT(setTextWeightDemi())); m_richTextActions << createAction("svg_weight_black", SLOT(setTextWeightBlack())); m_richTextActions << createAction("svg_increase_font_size", SLOT(increaseTextSize())); m_richTextActions << createAction("svg_decrease_font_size", SLOT(decreaseTextSize())); m_richTextActions << createAction("svg_align_left", SLOT(alignLeft())); m_richTextActions << createAction("svg_align_right", SLOT(alignRight())); m_richTextActions << createAction("svg_align_center", SLOT(alignCenter())); // m_richTextActions << createAction("svg_align_justified", // SLOT(alignJustified())); // Settings m_richTextActions << createAction("svg_settings", SLOT(setSettings())); QWidgetAction *fontComboAction = new QWidgetAction(this); fontComboAction->setToolTip(i18n("Font")); KisFontComboBoxes *fontCombo = new KisFontComboBoxes(); connect(fontCombo, SIGNAL(fontChanged(QString)), SLOT(setFont(QString))); fontComboAction->setDefaultWidget(fontCombo); actionCollection()->addAction("svg_font", fontComboAction); m_richTextActions << fontComboAction; actionRegistry->propertizeAction("svg_font", fontComboAction); QWidgetAction *fontSizeAction = new FontSizeAction(this); fontSizeAction->setToolTip(i18n("Size")); connect(fontSizeAction, SIGNAL(fontSizeChanged(qreal)), this, SLOT(setFontSize(qreal))); actionCollection()->addAction("svg_font_size", fontSizeAction); m_richTextActions << fontSizeAction; actionRegistry->propertizeAction("svg_font_size", fontSizeAction); KoColorPopupAction *fgColor = new KoColorPopupAction(this); fgColor->setCurrentColor(QColor(Qt::black)); fgColor->setToolTip(i18n("Text Color")); connect(fgColor, SIGNAL(colorChanged(KoColor)), SLOT(setFontColor(KoColor))); actionCollection()->addAction("svg_format_textcolor", fgColor); m_richTextActions << fgColor; actionRegistry->propertizeAction("svg_format_textcolor", fgColor); KoColorPopupAction *bgColor = new KoColorPopupAction(this); bgColor->setCurrentColor(QColor(Qt::white)); bgColor->setToolTip(i18n("Background Color")); connect(bgColor, SIGNAL(colorChanged(KoColor)), SLOT(setBackgroundColor(KoColor))); actionCollection()->addAction("svg_background_color", bgColor); actionRegistry->propertizeAction("svg_background_color", bgColor); m_richTextActions << bgColor; QWidgetAction *colorPickerAction = new QWidgetAction(this); colorPickerAction->setToolTip(i18n("Pick a Color")); KisScreenColorPicker *colorPicker = new KisScreenColorPicker(false); connect(colorPicker, SIGNAL(sigNewColorPicked(KoColor)), fgColor, SLOT(setCurrentColor(KoColor))); connect(colorPicker, SIGNAL(sigNewColorPicked(KoColor)), SLOT(setFontColor(KoColor))); colorPickerAction->setDefaultWidget(colorPicker); actionCollection()->addAction("svg_pick_color", colorPickerAction); m_richTextActions << colorPickerAction; actionRegistry->propertizeAction("svg_pick_color", colorPickerAction); QWidgetAction *lineHeight = new QWidgetAction(this); QDoubleSpinBox *spnLineHeight = new QDoubleSpinBox(); spnLineHeight->setToolTip(i18n("Line height")); spnLineHeight->setRange(0.0, 1000.0); spnLineHeight->setSingleStep(10.0); spnLineHeight->setSuffix(i18n("%")); connect(spnLineHeight, SIGNAL(valueChanged(double)), SLOT(setLineHeight(double))); lineHeight->setDefaultWidget(spnLineHeight); actionCollection()->addAction("svg_line_height", lineHeight); m_richTextActions << lineHeight; actionRegistry->propertizeAction("svg_line_height", lineHeight); QWidgetAction *letterSpacing = new QWidgetAction(this); QDoubleSpinBox *spnletterSpacing = new QDoubleSpinBox(); spnletterSpacing->setToolTip(i18n("Letter Spacing")); spnletterSpacing->setRange(-20.0, 20.0); spnletterSpacing->setSingleStep(0.5); connect(spnletterSpacing, SIGNAL(valueChanged(double)), SLOT(setLetterSpacing(double))); letterSpacing->setDefaultWidget(spnletterSpacing); actionCollection()->addAction("svg_letter_spacing", letterSpacing); m_richTextActions << letterSpacing; actionRegistry->propertizeAction("svg_letter_spacing", letterSpacing); } void SvgTextEditor::enableRichTextActions(bool enable) { Q_FOREACH(QAction *action, m_richTextActions) { action->setEnabled(enable); } } void SvgTextEditor::enableSvgTextActions(bool enable) { Q_FOREACH(QAction *action, m_svgTextActions) { action->setEnabled(enable); } } void SvgTextEditor::slotCloseEditor() { close(); emit textEditorClosed(); } diff --git a/plugins/tools/svgtexttool/kis_font_family_combo_box.cpp b/plugins/tools/svgtexttool/kis_font_family_combo_box.cpp index 9fc7aae48f..b789d9094a 100644 --- a/plugins/tools/svgtexttool/kis_font_family_combo_box.cpp +++ b/plugins/tools/svgtexttool/kis_font_family_combo_box.cpp @@ -1,303 +1,306 @@ /* This file is part of the KDE project * * Copyright 2017 Wolthera van Hövell tot Westerflier * * 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_font_family_combo_box.h" #include #include #include #include #include #include #include #include #include #include #include PinnedFontsSeparator::PinnedFontsSeparator(QAbstractItemDelegate *_default, QWidget *parent) : QStyledItemDelegate(parent), m_separatorIndex(0), m_separatorAdded(false), m_defaultDelegate(_default) { } void PinnedFontsSeparator::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (index.row() == m_separatorIndex && m_separatorAdded) { QRect viewRect = option.rect; painter->setPen(Qt::gray); painter->drawLine((viewRect.topLeft() + viewRect.bottomLeft()) / 2 + QPoint(5, 0), (viewRect.topRight() + viewRect.bottomRight()) / 2 - QPoint(5, 0)); } else { m_defaultDelegate->paint(painter, option, index); } } QSize PinnedFontsSeparator::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { return QStyledItemDelegate::sizeHint(option, index) * 1.25; } void PinnedFontsSeparator::setSeparatorIndex(int index) { m_separatorIndex = index; } void PinnedFontsSeparator::setSeparatorAdded() { m_separatorAdded = true; } KisFontFamilyComboBox::KisFontFamilyComboBox(QWidget *parent) : QComboBox(parent), m_initilized(false), m_initializeFromConfig(false) { setEditable(true); completer()->setCompletionMode(QCompleter::InlineCompletion); completer()->setCaseSensitivity(Qt::CaseInsensitive); // The following are all helper fonts for LaTeX that no one but LaTeX would use // but because many people use LaTeX, they do show up on quite a few systems. m_blacklistedFonts << "bbold10" << "cmbsy10" << "cmmib10" << "cmss10" << "cmex10" << "cmmi10" << "cmr10" << "cmsy10" << "eufb10" << "eufm10" << "eurb10" << "eurm10" << "esint10" << "eufm10" << "eusb10" << "eusm10" << "lasy10" << "lasyb10" << "msam10" << "msbm10" << "rsfs10" << "stmary10" << "wasy10" << "wasyb10"; refillComboBox(); - QFontComboBox *temp = new QFontComboBox(); + QFontComboBox *temp = new QFontComboBox(this); m_fontSeparator = new PinnedFontsSeparator(temp->itemDelegate(), this); temp->setEnabled(true); temp->hide(); m_separatorIndex = 0; m_pinnedFonts = KisConfig(true).readList("PinnedFonts", QStringList{}); } void KisFontFamilyComboBox::refillComboBox(QVector writingSystems) { QFontDatabase fonts = QFontDatabase(); int maxWidth = 0; this->clear(); QStringList duplicateFonts; QStringList filteredFonts; if (writingSystems.isEmpty()) { writingSystems.append(QFontDatabase::Any); } for (int i = 0; i < writingSystems.size(); i++) { Q_FOREACH (QString family, fonts.families(writingSystems.at(i))) { // if it's a private family it shouldn't be added. bool addFont = !fonts.isPrivateFamily(family); if (addFont && filteredFonts.contains(family)) { addFont = false; } if (addFont && duplicateFonts.contains(family)) { addFont = false; } if (addFont && m_blacklistedFonts.contains(family)) { addFont = false; } if (addFont && !fonts.isSmoothlyScalable(family)) { addFont = false; } if (addFont) { // now, check for all possible familyname+style name combinations, so we can avoid those. Q_FOREACH (const QString style, fonts.styles(family)) { duplicateFonts.append(family + " " + style); duplicateFonts.append(family + "_" + style); } filteredFonts.append(family); int width = 1.5 * view()->fontMetrics() .width(family + " " + fonts.writingSystemSample(QFontDatabase::Any)); if (width > maxWidth) { maxWidth = width; } } } } this->addItems(filteredFonts); if (this->count() > this->maxVisibleItems()) { maxWidth += view()->style()->pixelMetric(QStyle::PixelMetric::PM_ScrollBarExtent); } view()->setMinimumWidth(maxWidth); } // KisFontFamilyComboBox::refillComboBox void KisFontFamilyComboBox::setTopFont(const QString &family) { if (family.isEmpty() || !m_initilized || m_pinnedFonts.contains(family)) { return; } if (m_pinnedFonts.count() > 4) { this->removeItem(4); m_pinnedFonts.pop_back(); m_separatorIndex--; } if (m_pinnedFonts.isEmpty()) { this->insertSeparator(0); m_fontSeparator->setSeparatorAdded(); } m_pinnedFonts.push_front(family); this->insertItem(0, family); m_separatorIndex++; m_fontSeparator->setSeparatorIndex(m_separatorIndex); KisConfig(false).writeList("PinnedFonts", m_pinnedFonts); } void KisFontFamilyComboBox::setInitialized() { + if(m_initilized) + return; + m_initilized = true; for(int i=m_pinnedFonts.count()-1; i>=0; i--){ this->insertItem(0, m_pinnedFonts[i]); m_separatorIndex++; } if(m_pinnedFonts.count() > 0){ this->insertSeparator(m_separatorIndex); m_fontSeparator->setSeparatorIndex(m_separatorIndex); m_fontSeparator->setSeparatorAdded(); } this->setItemDelegate(m_fontSeparator); } KisFontComboBoxes::KisFontComboBoxes(QWidget *parent) : QWidget(parent) { QHBoxLayout *layout = new QHBoxLayout(); this->setLayout(layout); m_family = new KisFontFamilyComboBox(); m_family->setMinimumWidth(100); m_family->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); layout->addWidget(m_family); m_styles = new QComboBox(); layout->addWidget(m_styles); fontFamilyChanged(); m_family->setToolTip(i18n("Font Family")); m_styles->setToolTip(i18n("Font Style")); connect(m_family, SIGNAL(currentTextChanged(QString)), this, SLOT(fontFamilyChanged())); connect(m_family, SIGNAL(currentTextChanged(QString)), this, SLOT(fontChange())); connect(m_styles, SIGNAL(activated(int)), this, SLOT(fontChange())); } void KisFontComboBoxes::setCurrentFont(QFont font) { setCurrentFamily(font.family()); setCurrentStyle(QFontDatabase().styleString(font)); } void KisFontComboBoxes::setCurrentFamily(const QString family) { m_family->setCurrentText(family); fontFamilyChanged(); } void KisFontComboBoxes::setCurrentStyle(QString style) { QString properStyle = QString(); for (int i = 0; i < m_styles->count(); i++) { QString item = m_styles->itemText(i); if (item == style) { properStyle = style; } else if (item.contains(style, Qt::CaseInsensitive)) { properStyle = item; } else if (item.contains("regular", Qt::CaseInsensitive)) { properStyle = item; } } m_styles->setCurrentText(properStyle); } QString KisFontComboBoxes::currentFamily() const { return m_family->currentText(); } QString KisFontComboBoxes::currentStyle() const { return m_styles->currentText(); } QFont KisFontComboBoxes::currentFont(int pointSize) const { return QFontDatabase().font(m_family->currentText(), m_styles->currentText(), pointSize); } void KisFontComboBoxes::refillComboBox(QVector writingSystems) { KisFontFamilyComboBox *cmb = qobject_cast(m_family); cmb->refillComboBox(writingSystems); } void KisFontComboBoxes::fontFamilyChanged() { QString currentText = m_styles->currentText(); QFontDatabase fonts; const QString family = m_family->currentText(); int maxWidth = 0; m_styles->clear(); QStringList styles; KisFontFamilyComboBox *cmb = qobject_cast(m_family); cmb->setTopFont(family); if (fonts.styles(family).isEmpty()) { styles.append("Normal"); } Q_FOREACH (const QString style, fonts.styles(family)) { int b = fonts.weight(family, style); int bindex = 0; for (int i = 0; i < styles.size(); i++) { if (b > fonts.weight(family, styles.at(i))) { bindex = i; } } if (!styles.contains(style)) { styles.insert(bindex, style); maxWidth = qMax(m_styles->view()->fontMetrics().width(style + " "), maxWidth); } } m_styles->addItems(styles); if (m_styles->count() > m_styles->maxVisibleItems()) { maxWidth += m_styles->view()->style()->pixelMetric(QStyle::PixelMetric::PM_ScrollBarExtent); } m_styles->view()->setMinimumWidth(maxWidth); if (styles.contains(currentText)) { m_styles->setCurrentText(currentText); } } // KisFontComboBoxes::fontFamilyChanged void KisFontComboBoxes::fontChange() { emit fontChanged(currentFont(10).toString()); } void KisFontComboBoxes::setInitialized() { KisFontFamilyComboBox *cmb = qobject_cast(m_family); cmb->setInitialized(); } diff --git a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush.cpp b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush.cpp index a92e804783..815c96e414 100644 --- a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush.cpp +++ b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush.cpp @@ -1,350 +1,359 @@ /* * 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_tool_lazy_brush.h" #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_cursor.h" #include "kis_config.h" #include "kundo2magicstring.h" #include "KoProperties.h" #include "kis_node_manager.h" #include "kis_layer_properties_icons.h" #include "kis_canvas_resource_provider.h" #include "kis_tool_lazy_brush_options_widget.h" #include "lazybrush/kis_colorize_mask.h" #include "kis_signal_auto_connection.h" struct KisToolLazyBrush::Private { bool activateMaskMode = false; bool oldShowKeyStrokesValue = false; bool oldShowColoringValue = false; KisNodeWSP manuallyActivatedNode; KisSignalAutoConnectionsStore toolConnections; }; KisToolLazyBrush::KisToolLazyBrush(KoCanvasBase * canvas) : KisToolFreehand(canvas, KisCursor::load("tool_freehand_cursor.png", 5, 5), kundo2_i18n("Colorize Mask Key Stroke")), m_d(new Private) { setObjectName("tool_lazybrush"); } KisToolLazyBrush::~KisToolLazyBrush() { } void KisToolLazyBrush::tryDisableKeyStrokesOnMask() { // upgrade to strong pointer KisNodeSP manuallyActivatedNode = m_d->manuallyActivatedNode; if (manuallyActivatedNode) { KisLayerPropertiesIcons::setNodeProperty(manuallyActivatedNode, KisLayerPropertiesIcons::colorizeEditKeyStrokes, false, image()); manuallyActivatedNode = 0; } m_d->manuallyActivatedNode = 0; } void KisToolLazyBrush::activate(ToolActivation activation, const QSet &shapes) { KisCanvas2 * kiscanvas = dynamic_cast(canvas()); m_d->toolConnections.addUniqueConnection( kiscanvas->viewManager()->canvasResourceProvider(), SIGNAL(sigNodeChanged(KisNodeSP)), this, SLOT(slotCurrentNodeChanged(KisNodeSP))); KisColorizeMask *mask = qobject_cast(currentNode().data()); if (mask) { mask->regeneratePrefilteredDeviceIfNeeded(); } KisToolFreehand::activate(activation, shapes); } void KisToolLazyBrush::deactivate() { KisToolFreehand::deactivate(); tryDisableKeyStrokesOnMask(); m_d->toolConnections.clear(); } void KisToolLazyBrush::slotCurrentNodeChanged(KisNodeSP node) { // upgrade to strong pointer KisNodeSP manuallyActivatedNode = m_d->manuallyActivatedNode; if (node != manuallyActivatedNode) { tryDisableKeyStrokesOnMask(); KisColorizeMask *mask = qobject_cast(node.data()); if (mask) { mask->regeneratePrefilteredDeviceIfNeeded(); } } } void KisToolLazyBrush::resetCursorStyle() { - KisToolFreehand::resetCursorStyle(); + // If there's no mask yet, we show the hand cursor + if (!colorizeMaskActive() && canCreateColorizeMask()) { + useCursor(KisCursor::handCursor()); + m_d->activateMaskMode = true; + setOutlineEnabled(false); + } + else { + KisToolFreehand::resetCursorStyle(); + } } bool KisToolLazyBrush::colorizeMaskActive() const { KisNodeSP node = currentNode(); return node && node->inherits("KisColorizeMask"); } bool KisToolLazyBrush::canCreateColorizeMask() const { KisNodeSP node = currentNode(); return node && node->inherits("KisLayer"); } bool KisToolLazyBrush::shouldActivateKeyStrokes() const { KisNodeSP node = currentNode(); return node && node->inherits("KisColorizeMask") && !KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool(); } void KisToolLazyBrush::tryCreateColorizeMask() { KisNodeSP node = currentNode(); if (!node) return; KoProperties properties; properties.setProperty("visible", true); properties.setProperty("locked", false); QList masks = node->childNodes(QStringList("KisColorizeMask"), properties); if (!masks.isEmpty()) { KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); viewManager->nodeManager()->slotNonUiActivatedNode(masks.first()); } else { KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); viewManager->nodeManager()->createNode("KisColorizeMask"); } } void KisToolLazyBrush::activatePrimaryAction() { KisToolFreehand::activatePrimaryAction(); - if (shouldActivateKeyStrokes() || - (!colorizeMaskActive() && canCreateColorizeMask())) { + qDebug() << "1"; + if (!colorizeMaskActive() && canCreateColorizeMask()) { + qDebug() << "2"; useCursor(KisCursor::handCursor()); m_d->activateMaskMode = true; setOutlineEnabled(false); } } void KisToolLazyBrush::deactivatePrimaryAction() { if (m_d->activateMaskMode) { m_d->activateMaskMode = false; setOutlineEnabled(true); resetCursorStyle(); } KisToolFreehand::deactivatePrimaryAction(); } void KisToolLazyBrush::beginPrimaryAction(KoPointerEvent *event) { if (m_d->activateMaskMode) { if (!colorizeMaskActive() && canCreateColorizeMask()) { tryCreateColorizeMask(); } else if (shouldActivateKeyStrokes()) { // upgrade to strong pointer KisNodeSP manuallyActivatedNode = m_d->manuallyActivatedNode; KisNodeSP node = currentNode(); KIS_SAFE_ASSERT_RECOVER_NOOP(!manuallyActivatedNode || manuallyActivatedNode == node); KisLayerPropertiesIcons::setNodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true, image()); m_d->manuallyActivatedNode = node; } } else { KisToolFreehand::beginPrimaryAction(event); } } void KisToolLazyBrush::continuePrimaryAction(KoPointerEvent *event) { if (m_d->activateMaskMode) return; KisToolFreehand::continuePrimaryAction(event); } void KisToolLazyBrush::endPrimaryAction(KoPointerEvent *event) { if (m_d->activateMaskMode) return; KisToolFreehand::endPrimaryAction(event); } void KisToolLazyBrush::activateAlternateAction(KisTool::AlternateAction action) { if (action == KisTool::Secondary && !m_d->activateMaskMode) { KisNodeSP node = currentNode(); if (!node) return; m_d->oldShowKeyStrokesValue = KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool(); KisLayerPropertiesIcons::setNodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, !m_d->oldShowKeyStrokesValue, image()); KisToolFreehand::activatePrimaryAction(); } else if (action == KisTool::Third && !m_d->activateMaskMode) { KisNodeSP node = currentNode(); if (!node) return; m_d->oldShowColoringValue = KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::colorizeShowColoring, true).toBool(); KisLayerPropertiesIcons::setNodeProperty(node, KisLayerPropertiesIcons::colorizeShowColoring, !m_d->oldShowColoringValue, image()); KisToolFreehand::activatePrimaryAction(); } else { KisToolFreehand::activateAlternateAction(action); } } void KisToolLazyBrush::deactivateAlternateAction(KisTool::AlternateAction action) { if (action == KisTool::Secondary && !m_d->activateMaskMode) { KisNodeSP node = currentNode(); if (!node) return; KisLayerPropertiesIcons::setNodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, m_d->oldShowKeyStrokesValue, image()); KisToolFreehand::deactivatePrimaryAction(); } else if (action == KisTool::Third && !m_d->activateMaskMode) { KisNodeSP node = currentNode(); if (!node) return; KisLayerPropertiesIcons::setNodeProperty(node, KisLayerPropertiesIcons::colorizeShowColoring, m_d->oldShowColoringValue, image()); KisToolFreehand::deactivatePrimaryAction(); } else { KisToolFreehand::deactivateAlternateAction(action); } } void KisToolLazyBrush::beginAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { if (!m_d->activateMaskMode && (action == KisTool::Secondary || action == KisTool::Third)) { beginPrimaryAction(event); } else { KisToolFreehand::beginAlternateAction(event, action); } } void KisToolLazyBrush::continueAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { if (!m_d->activateMaskMode && (action == KisTool::Secondary || action == KisTool::Third)) { continuePrimaryAction(event); } else { KisToolFreehand::continueAlternateAction(event, action); } } void KisToolLazyBrush::endAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { if (!m_d->activateMaskMode && (action == KisTool::Secondary || action == KisTool::Third)) { endPrimaryAction(event); } else { KisToolFreehand::endAlternateAction(event, action); } } void KisToolLazyBrush::explicitUserStrokeEndRequest() { if (m_d->activateMaskMode) { tryCreateColorizeMask(); } else if (colorizeMaskActive()) { KisNodeSP node = currentNode(); if (!node) return; KisLayerPropertiesIcons::setNodeProperty(node, KisLayerPropertiesIcons::colorizeNeedsUpdate, false, image()); } } QWidget * KisToolLazyBrush::createOptionWidget() { KisCanvas2 * kiscanvas = dynamic_cast(canvas()); QWidget *optionsWidget = new KisToolLazyBrushOptionsWidget(kiscanvas->viewManager()->canvasResourceProvider(), 0); optionsWidget->setObjectName(toolId() + "option widget"); // // See https://bugs.kde.org/show_bug.cgi?id=316896 // QWidget *specialSpacer = new QWidget(optionsWidget); // specialSpacer->setObjectName("SpecialSpacer"); // specialSpacer->setFixedSize(0, 0); // optionsWidget->layout()->addWidget(specialSpacer); return optionsWidget; } diff --git a/plugins/tools/tool_transform2/kis_tool_transform.cc b/plugins/tools/tool_transform2/kis_tool_transform.cc index 32ace87044..bc72332ef5 100644 --- a/plugins/tools/tool_transform2/kis_tool_transform.cc +++ b/plugins/tools/tool_transform2/kis_tool_transform.cc @@ -1,1125 +1,1126 @@ /* * kis_tool_transform.cc -- part of Krita * * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 C. Boemann * Copyright (c) 2010 Marc Pegon * 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.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 "widgets/kis_progress_widget.h" #include "kis_transform_utils.h" #include "kis_warp_transform_strategy.h" #include "kis_cage_transform_strategy.h" #include "kis_liquify_transform_strategy.h" #include "kis_free_transform_strategy.h" #include "kis_perspective_transform_strategy.h" #include "kis_transform_mask.h" #include "kis_transform_mask_adapter.h" #include "krita_container_utils.h" #include "kis_layer_utils.h" #include #include "strokes/transform_stroke_strategy.h" KisToolTransform::KisToolTransform(KoCanvasBase * canvas) : KisTool(canvas, KisCursor::rotateCursor()) , m_workRecursively(true) , m_warpStrategy( new KisWarpTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_cageStrategy( new KisCageTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_liquifyStrategy( new KisLiquifyTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction, canvas->resourceManager())) , m_freeStrategy( new KisFreeTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), dynamic_cast(canvas)->snapGuide(), m_currentArgs, m_transaction)) , m_perspectiveStrategy( new KisPerspectiveTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), dynamic_cast(canvas)->snapGuide(), m_currentArgs, m_transaction)) { m_canvas = dynamic_cast(canvas); Q_ASSERT(m_canvas); setObjectName("tool_transform"); useCursor(KisCursor::selectCursor()); m_optionsWidget = 0; warpAction = new KisAction(i18n("Warp")); liquifyAction = new KisAction(i18n("Liquify")); cageAction = new KisAction(i18n("Cage")); freeTransformAction = new KisAction(i18n("Free")); perspectiveAction = new KisAction(i18n("Perspective")); // extra actions for free transform that are in the tool options mirrorHorizontalAction = new KisAction(i18n("Mirror Horizontal")); mirrorVericalAction = new KisAction(i18n("Mirror Vertical")); rotateNinteyCWAction = new KisAction(i18n("Rotate 90 degrees Clockwise")); rotateNinteyCCWAction = new KisAction(i18n("Rotate 90 degrees CounterClockwise")); applyTransformation = new KisAction(i18n("Apply")); resetTransformation = new KisAction(i18n("Reset")); m_contextMenu.reset(new QMenu()); connect(m_warpStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_cageStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_liquifyStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_liquifyStrategy.data(), SIGNAL(requestCursorOutlineUpdate(QPointF)), SLOT(cursorOutlineUpdateRequested(QPointF))); connect(m_liquifyStrategy.data(), SIGNAL(requestUpdateOptionWidget()), SLOT(updateOptionWidget())); connect(m_freeStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_freeStrategy.data(), SIGNAL(requestResetRotationCenterButtons()), SLOT(resetRotationCenterButtonsRequested())); connect(m_freeStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool))); connect(m_perspectiveStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_perspectiveStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool))); connect(&m_changesTracker, SIGNAL(sigConfigChanged(KisToolChangesTrackerDataSP)), this, SLOT(slotTrackerChangedConfig(KisToolChangesTrackerDataSP))); } KisToolTransform::~KisToolTransform() { cancelStroke(); } void KisToolTransform::outlineChanged() { emit freeTransformChanged(); m_canvas->updateCanvas(); } void KisToolTransform::canvasUpdateRequested() { m_canvas->updateCanvas(); } void KisToolTransform::resetCursorStyle() { setFunctionalCursor(); } void KisToolTransform::resetRotationCenterButtonsRequested() { if (!m_optionsWidget) return; m_optionsWidget->resetRotationCenterButtons(); } void KisToolTransform::imageTooBigRequested(bool value) { if (!m_optionsWidget) return; m_optionsWidget->setTooBigLabelVisible(value); } KisTransformStrategyBase* KisToolTransform::currentStrategy() const { if (m_currentArgs.mode() == ToolTransformArgs::FREE_TRANSFORM) { return m_freeStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::WARP) { return m_warpStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::CAGE) { return m_cageStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) { return m_liquifyStrategy.data(); } else /* if (m_currentArgs.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) */ { return m_perspectiveStrategy.data(); } } void KisToolTransform::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if (!m_strokeId || !m_transaction.rootNode()) return; QRectF newRefRect = KisTransformUtils::imageToFlake(m_canvas->coordinatesConverter(), QRectF(0.0,0.0,1.0,1.0)); if (m_refRect != newRefRect) { m_refRect = newRefRect; currentStrategy()->externalConfigChanged(); } currentStrategy()->paint(gc); if (!m_cursorOutline.isEmpty()) { QPainterPath mappedOutline = KisTransformUtils::imageToFlakeTransform( m_canvas->coordinatesConverter()).map(m_cursorOutline); paintToolOutline(&gc, mappedOutline); } } void KisToolTransform::setFunctionalCursor() { if (overrideCursorIfNotEditable()) { return; } if (!m_strokeId) { useCursor(KisCursor::pointingHandCursor()); } else if (m_strokeId && !m_transaction.rootNode()) { // we are in the middle of stroke initialization useCursor(KisCursor::waitCursor()); } else { useCursor(currentStrategy()->getCurrentCursor()); } } void KisToolTransform::cursorOutlineUpdateRequested(const QPointF &imagePos) { QRect canvasUpdateRect; if (!m_cursorOutline.isEmpty()) { canvasUpdateRect = m_canvas->coordinatesConverter()-> imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect(); } m_cursorOutline = currentStrategy()-> getCursorOutline().translated(imagePos); if (!m_cursorOutline.isEmpty()) { canvasUpdateRect |= m_canvas->coordinatesConverter()-> imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect(); } if (!canvasUpdateRect.isEmpty()) { // grow rect a bit to follow interpolation fuzziness canvasUpdateRect = kisGrowRect(canvasUpdateRect, 2); m_canvas->updateCanvas(canvasUpdateRect); } } void KisToolTransform::beginActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (!nodeEditable()) { event->ignore(); return; } if (!m_strokeId) { startStroke(m_currentArgs.mode(), false); } else if (m_transaction.rootNode()) { bool result = false; if (usePrimaryAction) { result = currentStrategy()->beginPrimaryAction(event); } else { result = currentStrategy()->beginAlternateAction(event, action); } if (result) { setMode(KisTool::PAINT_MODE); } } m_actuallyMoveWhileSelected = false; outlineChanged(); } void KisToolTransform::continueActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (mode() != KisTool::PAINT_MODE) return; if (!m_transaction.rootNode()) return; m_actuallyMoveWhileSelected = true; if (usePrimaryAction) { currentStrategy()->continuePrimaryAction(event); } else { currentStrategy()->continueAlternateAction(event, action); } updateOptionWidget(); outlineChanged(); } void KisToolTransform::endActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (mode() != KisTool::PAINT_MODE) return; setMode(KisTool::HOVER_MODE); if (m_actuallyMoveWhileSelected || currentStrategy()->acceptsClicks()) { bool result = false; if (usePrimaryAction) { result = currentStrategy()->endPrimaryAction(event); } else { result = currentStrategy()->endAlternateAction(event, action); } if (result) { commitChanges(); } outlineChanged(); } updateOptionWidget(); updateApplyResetAvailability(); } QMenu* KisToolTransform::popupActionsMenu() { if (m_contextMenu) { m_contextMenu->clear(); m_contextMenu->addSection(i18n("Transform Tool Actions")); m_contextMenu->addSeparator(); // add a quick switch to different transform types m_contextMenu->addAction(freeTransformAction); m_contextMenu->addAction(perspectiveAction); m_contextMenu->addAction(warpAction); m_contextMenu->addAction(cageAction); m_contextMenu->addAction(liquifyAction); // extra options if free transform is selected if (transformMode() == FreeTransformMode) { m_contextMenu->addSeparator(); m_contextMenu->addAction(mirrorHorizontalAction); m_contextMenu->addAction(mirrorVericalAction); m_contextMenu->addAction(rotateNinteyCWAction); m_contextMenu->addAction(rotateNinteyCCWAction); } m_contextMenu->addSeparator(); m_contextMenu->addAction(applyTransformation); m_contextMenu->addAction(resetTransformation); } return m_contextMenu.data(); } void KisToolTransform::beginPrimaryAction(KoPointerEvent *event) { beginActionImpl(event, true, KisTool::NONE); } void KisToolTransform::continuePrimaryAction(KoPointerEvent *event) { continueActionImpl(event, true, KisTool::NONE); } void KisToolTransform::endPrimaryAction(KoPointerEvent *event) { endActionImpl(event, true, KisTool::NONE); } void KisToolTransform::activatePrimaryAction() { currentStrategy()->activatePrimaryAction(); setFunctionalCursor(); } void KisToolTransform::deactivatePrimaryAction() { currentStrategy()->deactivatePrimaryAction(); } void KisToolTransform::activateAlternateAction(AlternateAction action) { currentStrategy()->activateAlternateAction(action); setFunctionalCursor(); } void KisToolTransform::deactivateAlternateAction(AlternateAction action) { currentStrategy()->deactivateAlternateAction(action); } void KisToolTransform::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { beginActionImpl(event, false, action); } void KisToolTransform::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { continueActionImpl(event, false, action); } void KisToolTransform::endAlternateAction(KoPointerEvent *event, AlternateAction action) { endActionImpl(event, false, action); } void KisToolTransform::mousePressEvent(KoPointerEvent *event) { KisTool::mousePressEvent(event); } void KisToolTransform::mouseMoveEvent(KoPointerEvent *event) { QPointF mousePos = m_canvas->coordinatesConverter()->documentToImage(event->point); cursorOutlineUpdateRequested(mousePos); if (this->mode() != KisTool::PAINT_MODE) { currentStrategy()->hoverActionCommon(event); setFunctionalCursor(); KisTool::mouseMoveEvent(event); return; } } void KisToolTransform::mouseReleaseEvent(KoPointerEvent *event) { KisTool::mouseReleaseEvent(event); } void KisToolTransform::applyTransform() { slotApplyTransform(); } KisToolTransform::TransformToolMode KisToolTransform::transformMode() const { TransformToolMode mode = FreeTransformMode; switch (m_currentArgs.mode()) { case ToolTransformArgs::FREE_TRANSFORM: mode = FreeTransformMode; break; case ToolTransformArgs::WARP: mode = WarpTransformMode; break; case ToolTransformArgs::CAGE: mode = CageTransformMode; break; case ToolTransformArgs::LIQUIFY: mode = LiquifyTransformMode; break; case ToolTransformArgs::PERSPECTIVE_4POINT: mode = PerspectiveTransformMode; break; default: KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode"); } return mode; } double KisToolTransform::translateX() const { return m_currentArgs.transformedCenter().x(); } double KisToolTransform::translateY() const { return m_currentArgs.transformedCenter().y(); } double KisToolTransform::rotateX() const { return m_currentArgs.aX(); } double KisToolTransform::rotateY() const { return m_currentArgs.aY(); } double KisToolTransform::rotateZ() const { return m_currentArgs.aZ(); } double KisToolTransform::scaleX() const { return m_currentArgs.scaleX(); } double KisToolTransform::scaleY() const { return m_currentArgs.scaleY(); } double KisToolTransform::shearX() const { return m_currentArgs.shearX(); } double KisToolTransform::shearY() const { return m_currentArgs.shearY(); } KisToolTransform::WarpType KisToolTransform::warpType() const { switch(m_currentArgs.warpType()) { case KisWarpTransformWorker::AFFINE_TRANSFORM: return AffineWarpType; case KisWarpTransformWorker::RIGID_TRANSFORM: return RigidWarpType; case KisWarpTransformWorker::SIMILITUDE_TRANSFORM: return SimilitudeWarpType; default: return RigidWarpType; } } double KisToolTransform::warpFlexibility() const { return m_currentArgs.alpha(); } int KisToolTransform::warpPointDensity() const { return m_currentArgs.numPoints(); } void KisToolTransform::setTransformMode(KisToolTransform::TransformToolMode newMode) { ToolTransformArgs::TransformMode mode = ToolTransformArgs::FREE_TRANSFORM; switch (newMode) { case FreeTransformMode: mode = ToolTransformArgs::FREE_TRANSFORM; break; case WarpTransformMode: mode = ToolTransformArgs::WARP; break; case CageTransformMode: mode = ToolTransformArgs::CAGE; break; case LiquifyTransformMode: mode = ToolTransformArgs::LIQUIFY; break; case PerspectiveTransformMode: mode = ToolTransformArgs::PERSPECTIVE_4POINT; break; default: KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode"); } if( mode != m_currentArgs.mode() ) { if( newMode == FreeTransformMode ) { m_optionsWidget->slotSetFreeTransformModeButtonClicked( true ); } else if( newMode == WarpTransformMode ) { m_optionsWidget->slotSetWarpModeButtonClicked( true ); } else if( newMode == CageTransformMode ) { m_optionsWidget->slotSetCageModeButtonClicked( true ); } else if( newMode == LiquifyTransformMode ) { m_optionsWidget->slotSetLiquifyModeButtonClicked( true ); } else if( newMode == PerspectiveTransformMode ) { m_optionsWidget->slotSetPerspectiveModeButtonClicked( true ); } emit transformModeChanged(); } } void KisToolTransform::setRotateX( double rotation ) { m_currentArgs.setAX( normalizeAngle(rotation) ); } void KisToolTransform::setRotateY( double rotation ) { m_currentArgs.setAY( normalizeAngle(rotation) ); } void KisToolTransform::setRotateZ( double rotation ) { m_currentArgs.setAZ( normalizeAngle(rotation) ); } void KisToolTransform::setWarpType( KisToolTransform::WarpType type ) { switch( type ) { case RigidWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::RIGID_TRANSFORM); break; case AffineWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::AFFINE_TRANSFORM); break; case SimilitudeWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::SIMILITUDE_TRANSFORM); break; default: break; } } void KisToolTransform::setWarpFlexibility( double flexibility ) { m_currentArgs.setAlpha( flexibility ); } void KisToolTransform::setWarpPointDensity( int density ) { m_optionsWidget->slotSetWarpDensity(density); } void KisToolTransform::initTransformMode(ToolTransformArgs::TransformMode mode) { m_currentArgs = KisTransformUtils::resetArgsForMode(mode, m_currentArgs.filterId(), m_transaction); initGuiAfterTransformMode(); } void KisToolTransform::initGuiAfterTransformMode() { currentStrategy()->externalConfigChanged(); outlineChanged(); updateOptionWidget(); updateApplyResetAvailability(); setFunctionalCursor(); } void KisToolTransform::initThumbnailImage(KisPaintDeviceSP previewDevice) { QImage origImg; m_selectedPortionCache = previewDevice; QTransform thumbToImageTransform; const int maxSize = 2000; QRect srcRect(m_transaction.originalRect().toAlignedRect()); int x, y, w, h; srcRect.getRect(&x, &y, &w, &h); if (m_selectedPortionCache) { if (w > maxSize || h > maxSize) { qreal scale = qreal(maxSize) / (w > h ? w : h); QTransform scaleTransform = QTransform::fromScale(scale, scale); QRect thumbRect = scaleTransform.mapRect(m_transaction.originalRect()).toAlignedRect(); origImg = m_selectedPortionCache-> createThumbnail(thumbRect.width(), thumbRect.height(), srcRect, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); thumbToImageTransform = scaleTransform.inverted(); } else { origImg = m_selectedPortionCache->convertToQImage(0, x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); thumbToImageTransform = QTransform(); } } // init both strokes since the thumbnail is initialized only once // during the stroke m_freeStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_perspectiveStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_warpStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_cageStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_liquifyStrategy->setThumbnailImage(origImg, thumbToImageTransform); } void KisToolTransform::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); if (currentNode()) { m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); } startStroke(ToolTransformArgs::FREE_TRANSFORM, false); } void KisToolTransform::deactivate() { endStroke(); m_canvas->updateCanvas(); KisTool::deactivate(); } void KisToolTransform::requestUndoDuringStroke() { if (!m_strokeId || !m_transaction.rootNode()) return; if (m_changesTracker.isEmpty()) { cancelStroke(); } else { m_changesTracker.requestUndo(); } } void KisToolTransform::requestStrokeEnd() { endStroke(); } void KisToolTransform::requestStrokeCancellation() { if (!m_transaction.rootNode() || m_currentArgs.isIdentity()) { cancelStroke(); } else { slotResetTransform(); } } void KisToolTransform::startStroke(ToolTransformArgs::TransformMode mode, bool forceReset) { Q_ASSERT(!m_strokeId); // set up and null checks before we do anything KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); KisNodeSP currentNode = resources->currentNode(); if (!currentNode || !currentNode->isEditable()) return; m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); m_currentArgs = ToolTransformArgs(); // some layer types cannot be transformed. Give a message and return if a user tries it if (currentNode->inherits("KisColorizeMask") || currentNode->inherits("KisFileLayer") || currentNode->inherits("KisCloneLayer")) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", - "Layer type cannot use the transform tool"), + (currentNode->inherits("KisColorizeMask") ? + "Layer type cannot use the transform tool": "Use transform mask instead")), koIcon("object-locked"), 4000, KisFloatingMessage::High); return; } if (m_optionsWidget) { m_workRecursively = m_optionsWidget->workRecursively() || !currentNode->paintDevice(); } KisSelectionSP selection = resources->activeSelection(); /** * When working with transform mask, selections are not * taken into account. */ if (selection && dynamic_cast(currentNode.data())) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Selections are not used when editing transform masks "), QIcon(), 4000, KisFloatingMessage::Low); selection = 0; } TransformStrokeStrategy *strategy = new TransformStrokeStrategy(mode, m_workRecursively, m_currentArgs.filterId(), forceReset, currentNode, selection, image().data(), image().data()); connect(strategy, SIGNAL(sigPreviewDeviceReady(KisPaintDeviceSP)), SLOT(slotPreviewDeviceGenerated(KisPaintDeviceSP))); connect(strategy, SIGNAL(sigTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)), SLOT(slotTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*))); // save unique identifier of the stroke so we could // recognize it when sigTransactionGenerated() is - // recieved (theoretically, the user can start two + // received (theoretically, the user can start two // strokes at the same time, if he is quick enough) m_strokeStrategyCookie = strategy; m_strokeId = image()->startStroke(strategy); KIS_SAFE_ASSERT_RECOVER_NOOP(m_changesTracker.isEmpty()); slotPreviewDeviceGenerated(0); } void KisToolTransform::endStroke() { if (!m_strokeId) return; if (m_transaction.rootNode() && !m_currentArgs.isIdentity()) { image()->addJob(m_strokeId, new TransformStrokeStrategy::TransformAllData(m_currentArgs)); } image()->endStroke(m_strokeId); m_strokeStrategyCookie = 0; m_strokeId.clear(); m_changesTracker.reset(); m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); outlineChanged(); } void KisToolTransform::slotTransactionGenerated(TransformTransactionProperties transaction, ToolTransformArgs args, void *strokeStrategyCookie) { if (!m_strokeId || strokeStrategyCookie != m_strokeStrategyCookie) return; if (transaction.transformedNodes().isEmpty()) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Cannot transform empty layer "), QIcon(), 1000, KisFloatingMessage::Medium); cancelStroke(); return; } m_transaction = transaction; m_currentArgs = args; m_transaction.setCurrentConfigLocation(&m_currentArgs); KIS_SAFE_ASSERT_RECOVER_NOOP(m_changesTracker.isEmpty()); commitChanges(); initGuiAfterTransformMode(); if (m_transaction.hasInvisibleNodes()) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Invisible sublayers will also be transformed. Lock layers if you do not want them to be transformed "), QIcon(), 4000, KisFloatingMessage::Low); } } void KisToolTransform::slotPreviewDeviceGenerated(KisPaintDeviceSP device) { if (device && device->exactBounds().isEmpty()) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Cannot transform empty layer "), QIcon(), 1000, KisFloatingMessage::Medium); cancelStroke(); } else { initThumbnailImage(device); initGuiAfterTransformMode(); } } void KisToolTransform::cancelStroke() { if (!m_strokeId) return; image()->cancelStroke(m_strokeId); m_strokeStrategyCookie = 0; m_strokeId.clear(); m_changesTracker.reset(); m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); outlineChanged(); } void KisToolTransform::commitChanges() { if (!m_strokeId || !m_transaction.rootNode()) return; m_changesTracker.commitConfig(toQShared(m_currentArgs.clone())); } void KisToolTransform::slotTrackerChangedConfig(KisToolChangesTrackerDataSP status) { const ToolTransformArgs *newArgs = dynamic_cast(status.data()); KIS_SAFE_ASSERT_RECOVER_RETURN(newArgs); *m_transaction.currentConfig() = *newArgs; slotUiChangedConfig(); updateOptionWidget(); } QList KisToolTransform::fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive) { QList result; auto fetchFunc = [&result, mode, root] (KisNodeSP node) { if (node->isEditable(node == root) && (!node->inherits("KisShapeLayer") || mode == ToolTransformArgs::FREE_TRANSFORM) && !node->inherits("KisFileLayer") && (!node->inherits("KisTransformMask") || node == root)) { result << node; } }; if (recursive) { KisLayerUtils::recursiveApplyNodes(root, fetchFunc); } else { fetchFunc(root); } return result; } QWidget* KisToolTransform::createOptionWidget() { if (!m_canvas) return 0; m_optionsWidget = new KisToolTransformConfigWidget(&m_transaction, m_canvas, m_workRecursively, 0); Q_CHECK_PTR(m_optionsWidget); m_optionsWidget->setObjectName(toolId() + " option widget"); // 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); connect(m_optionsWidget, SIGNAL(sigConfigChanged()), this, SLOT(slotUiChangedConfig())); connect(m_optionsWidget, SIGNAL(sigApplyTransform()), this, SLOT(slotApplyTransform())); connect(m_optionsWidget, SIGNAL(sigResetTransform()), this, SLOT(slotResetTransform())); connect(m_optionsWidget, SIGNAL(sigRestartTransform()), this, SLOT(slotRestartTransform())); connect(m_optionsWidget, SIGNAL(sigEditingFinished()), this, SLOT(slotEditingFinished())); connect(mirrorHorizontalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipX())); connect(mirrorVericalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipY())); connect(rotateNinteyCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCW())); connect(rotateNinteyCCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCCW())); connect(warpAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToWarpType())); connect(perspectiveAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToPerspectiveType())); connect(freeTransformAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToFreeTransformType())); connect(liquifyAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToLiquifyType())); connect(cageAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToCageType())); connect(applyTransformation, SIGNAL(triggered(bool)), this, SLOT(slotApplyTransform())); connect(resetTransformation, SIGNAL(triggered(bool)), this, SLOT(slotResetTransform())); updateOptionWidget(); return m_optionsWidget; } void KisToolTransform::updateOptionWidget() { if (!m_optionsWidget) return; if (!currentNode()) { m_optionsWidget->setEnabled(false); return; } else { m_optionsWidget->setEnabled(true); m_optionsWidget->updateConfig(m_currentArgs); } } void KisToolTransform::updateApplyResetAvailability() { if (m_optionsWidget) { m_optionsWidget->setApplyResetDisabled(m_currentArgs.isIdentity()); } } void KisToolTransform::slotUiChangedConfig() { if (mode() == KisTool::PAINT_MODE) return; currentStrategy()->externalConfigChanged(); if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) { m_currentArgs.saveLiquifyTransformMode(); } outlineChanged(); updateApplyResetAvailability(); } void KisToolTransform::slotApplyTransform() { QApplication::setOverrideCursor(KisCursor::waitCursor()); endStroke(); QApplication::restoreOverrideCursor(); } void KisToolTransform::slotResetTransform() { if (!m_strokeId || !m_transaction.rootNode()) return; if (m_currentArgs.continuedTransform()) { ToolTransformArgs::TransformMode savedMode = m_currentArgs.mode(); /** * Our reset transform button can be used for two purposes: * * 1) Reset current transform to the initial one, which was * loaded from the previous user action. * * 2) Reset transform frame to infinity when the frame is unchanged */ const bool transformDiffers = !m_currentArgs.continuedTransform()->isSameMode(m_currentArgs); if (transformDiffers && m_currentArgs.continuedTransform()->mode() == savedMode) { m_currentArgs.restoreContinuedState(); initGuiAfterTransformMode(); slotEditingFinished(); } else { KisNodeSP root = m_transaction.rootNode() ? m_transaction.rootNode() : image()->root(); cancelStroke(); startStroke(savedMode, true); KIS_ASSERT_RECOVER_NOOP(!m_currentArgs.continuedTransform()); } } else { initTransformMode(m_currentArgs.mode()); slotEditingFinished(); } } void KisToolTransform::slotRestartTransform() { if (!m_strokeId || !m_transaction.rootNode()) return; KisNodeSP root = m_transaction.rootNode(); KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above ToolTransformArgs savedArgs(m_currentArgs); cancelStroke(); startStroke(savedArgs.mode(), true); } void KisToolTransform::slotEditingFinished() { commitChanges(); } void KisToolTransform::slotUpdateToWarpType() { setTransformMode(KisToolTransform::TransformToolMode::WarpTransformMode); } void KisToolTransform::slotUpdateToPerspectiveType() { setTransformMode(KisToolTransform::TransformToolMode::PerspectiveTransformMode); } void KisToolTransform::slotUpdateToFreeTransformType() { setTransformMode(KisToolTransform::TransformToolMode::FreeTransformMode); } void KisToolTransform::slotUpdateToLiquifyType() { setTransformMode(KisToolTransform::TransformToolMode::LiquifyTransformMode); } void KisToolTransform::slotUpdateToCageType() { setTransformMode(KisToolTransform::TransformToolMode::CageTransformMode); } void KisToolTransform::setShearY(double shear) { m_optionsWidget->slotSetShearY(shear); } void KisToolTransform::setShearX(double shear) { m_optionsWidget->slotSetShearX(shear); } void KisToolTransform::setScaleY(double scale) { m_optionsWidget->slotSetScaleY(scale); } void KisToolTransform::setScaleX(double scale) { m_optionsWidget->slotSetScaleX(scale); } void KisToolTransform::setTranslateY(double translation) { m_optionsWidget->slotSetTranslateY(translation); } void KisToolTransform::setTranslateX(double translation) { m_optionsWidget->slotSetTranslateX(translation); } diff --git a/plugins/tools/tool_transform2/kis_transform_utils.cpp b/plugins/tools/tool_transform2/kis_transform_utils.cpp index 1fbbd525be..8a30d79d36 100644 --- a/plugins/tools/tool_transform2/kis_transform_utils.cpp +++ b/plugins/tools/tool_transform2/kis_transform_utils.cpp @@ -1,490 +1,490 @@ /* * 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_transform_utils.h" #include #include #include #include "tool_transform_args.h" #include "kis_paint_device.h" #include "kis_algebra_2d.h" #include "transform_transaction_properties.h" struct TransformTransactionPropertiesRegistrar { TransformTransactionPropertiesRegistrar() { qRegisterMetaType("TransformTransactionProperties"); } }; static TransformTransactionPropertiesRegistrar __registrar1; struct ToolTransformArgsRegistrar { ToolTransformArgsRegistrar() { qRegisterMetaType("ToolTransformArgs"); } }; static ToolTransformArgsRegistrar __registrar2; struct QPainterPathRegistrar { QPainterPathRegistrar() { qRegisterMetaType("QPainterPath"); } }; static QPainterPathRegistrar __registrar3; const int KisTransformUtils::rotationHandleVisualRadius = 12; const int KisTransformUtils::rotationHandleRadius = 8; const int KisTransformUtils::handleVisualRadius = 12; const int KisTransformUtils::handleRadius = 8; QTransform KisTransformUtils::imageToFlakeTransform(const KisCoordinatesConverter *converter) { return converter->imageToDocumentTransform() * converter->documentToFlakeTransform(); } qreal KisTransformUtils::effectiveHandleGrabRadius(const KisCoordinatesConverter *converter) { QPointF handleRadiusPt = flakeToImage(converter, QPointF(handleRadius, handleRadius)); return (handleRadiusPt.x() > handleRadiusPt.y()) ? handleRadiusPt.x() : handleRadiusPt.y(); } qreal KisTransformUtils::effectiveRotationHandleGrabRadius(const KisCoordinatesConverter *converter) { QPointF handleRadiusPt = flakeToImage(converter, QPointF(rotationHandleRadius, rotationHandleRadius)); return (handleRadiusPt.x() > handleRadiusPt.y()) ? handleRadiusPt.x() : handleRadiusPt.y(); } qreal KisTransformUtils::scaleFromAffineMatrix(const QTransform &t) { return KoUnit::approxTransformScale(t); } qreal KisTransformUtils::scaleFromPerspectiveMatrixX(const QTransform &t, const QPointF &basePt) { const QPointF pt = basePt + QPointF(1.0, 0); return kisDistance(t.map(pt), t.map(basePt)); } qreal KisTransformUtils::scaleFromPerspectiveMatrixY(const QTransform &t, const QPointF &basePt) { const QPointF pt = basePt + QPointF(0, 1.0); return kisDistance(t.map(pt), t.map(basePt)); } qreal KisTransformUtils::effectiveSize(const QRectF &rc) { return 0.5 * (rc.width() + rc.height()); } bool KisTransformUtils::thumbnailTooSmall(const QTransform &resultThumbTransform, const QRect &originalImageRect) { return KisAlgebra2D::minDimension(resultThumbTransform.mapRect(originalImageRect)) < 32; } QRectF handleRectImpl(qreal radius, const QTransform &t, const QRectF &limitingRect, const QPointF &basePoint, qreal *dOutX, qreal *dOutY) { const qreal handlesExtraScaleX = KisTransformUtils::scaleFromPerspectiveMatrixX(t, basePoint); const qreal handlesExtraScaleY = KisTransformUtils::scaleFromPerspectiveMatrixY(t, basePoint); const qreal maxD = 0.2 * KisTransformUtils::effectiveSize(limitingRect); const qreal dX = qMin(maxD, radius / handlesExtraScaleX); const qreal dY = qMin(maxD, radius / handlesExtraScaleY); QRectF handleRect(-0.5 * dX, -0.5 * dY, dX, dY); if (dOutX) { *dOutX = dX; } if (dOutY) { *dOutY = dY; } return handleRect; } QRectF KisTransformUtils::handleRect(qreal radius, const QTransform &t, const QRectF &limitingRect, qreal *dOutX, qreal *dOutY) { return handleRectImpl(radius, t, limitingRect, limitingRect.center(), dOutX, dOutY); } QRectF KisTransformUtils::handleRect(qreal radius, const QTransform &t, const QRectF &limitingRect, const QPointF &basePoint) { return handleRectImpl(radius, t, limitingRect, basePoint, 0, 0); } QPointF KisTransformUtils::clipInRect(QPointF p, QRectF r) { QPointF center = r.center(); QPointF t = p - center; r.translate(- center); if (t.y() != 0) { if (t.x() != 0) { double slope = t.y() / t.x(); if (t.x() < r.left()) { t.setY(r.left() * slope); t.setX(r.left()); } else if (t.x() > r.right()) { t.setY(r.right() * slope); t.setX(r.right()); } if (t.y() < r.top()) { t.setX(r.top() / slope); t.setY(r.top()); } else if (t.y() > r.bottom()) { t.setX(r.bottom() / slope); t.setY(r.bottom()); } } else { if (t.y() < r.top()) t.setY(r.top()); else if (t.y() > r.bottom()) t.setY(r.bottom()); } } else { if (t.x() < r.left()) t.setX(r.left()); else if (t.x() > r.right()) t.setX(r.right()); } t += center; return t; } KisTransformUtils::MatricesPack::MatricesPack(const ToolTransformArgs &args) { TS = QTransform::fromTranslate(-args.originalCenter().x(), -args.originalCenter().y()); SC = QTransform::fromScale(args.scaleX(), args.scaleY()); S.shear(0, args.shearY()); S.shear(args.shearX(), 0); if (args.mode() == ToolTransformArgs::FREE_TRANSFORM) { P.rotate(180. * args.aX() / M_PI, QVector3D(1, 0, 0)); P.rotate(180. * args.aY() / M_PI, QVector3D(0, 1, 0)); P.rotate(180. * args.aZ() / M_PI, QVector3D(0, 0, 1)); projectedP = P.toTransform(args.cameraPos().z()); } else if (args.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) { projectedP = args.flattenedPerspectiveTransform(); P = QMatrix4x4(projectedP); } QPointF translation = args.transformedCenter(); T = QTransform::fromTranslate(translation.x(), translation.y()); } QTransform KisTransformUtils::MatricesPack::finalTransform() const { return TS * SC * S * projectedP * T; } bool KisTransformUtils::checkImageTooBig(const QRectF &bounds, const MatricesPack &m) { bool imageTooBig = false; QMatrix4x4 unprojectedMatrix = QMatrix4x4(m.T) * m.P * QMatrix4x4(m.TS * m.SC * m.S); QVector points; points << bounds.topLeft(); points << bounds.topRight(); points << bounds.bottomRight(); points << bounds.bottomLeft(); Q_FOREACH (const QPointF &pt, points) { QVector4D v(pt.x(), pt.y(), 0, 1); v = unprojectedMatrix * v; qreal z = v.z() / v.w(); imageTooBig = z > 1024.0; if (imageTooBig) { break; } } return imageTooBig; } #include #include #include #include #include KisTransformWorker KisTransformUtils::createTransformWorker(const ToolTransformArgs &config, KisPaintDeviceSP device, KoUpdaterPtr updater, QVector3D *transformedCenter /* OUT */) { { KisTransformWorker t(0, config.scaleX(), config.scaleY(), config.shearX(), config.shearY(), config.originalCenter().x(), config.originalCenter().y(), config.aZ(), 0, // set X and Y translation 0, // to null for calculation 0, config.filter()); *transformedCenter = QVector3D(t.transform().map(config.originalCenter())); } QPointF translation = config.transformedCenter() - (*transformedCenter).toPointF(); KisTransformWorker transformWorker(device, config.scaleX(), config.scaleY(), config.shearX(), config.shearY(), config.originalCenter().x(), config.originalCenter().y(), config.aZ(), (int)(translation.x()), (int)(translation.y()), updater, config.filter()); return transformWorker; } void KisTransformUtils::transformDevice(const ToolTransformArgs &config, KisPaintDeviceSP device, KisProcessingVisitor::ProgressHelper *helper) { if (config.mode() == ToolTransformArgs::WARP) { KoUpdaterPtr updater = helper->updater(); KisWarpTransformWorker worker(config.warpType(), device, config.origPoints(), config.transfPoints(), config.alpha(), updater); worker.run(); } else if (config.mode() == ToolTransformArgs::CAGE) { KoUpdaterPtr updater = helper->updater(); KisCageTransformWorker worker(device, config.origPoints(), updater, config.pixelPrecision()); worker.prepareTransform(); worker.setTransformedCage(config.transfPoints()); worker.run(); - } else if (config.mode() == ToolTransformArgs::LIQUIFY) { + } else if (config.mode() == ToolTransformArgs::LIQUIFY && config.liquifyWorker()) { KoUpdaterPtr updater = helper->updater(); //FIXME: Q_UNUSED(updater); config.liquifyWorker()->run(device); } else { QVector3D transformedCenter; KoUpdaterPtr updater1 = helper->updater(); KoUpdaterPtr updater2 = helper->updater(); KisTransformWorker transformWorker = createTransformWorker(config, device, updater1, &transformedCenter); transformWorker.run(); if (config.mode() == ToolTransformArgs::FREE_TRANSFORM) { KisPerspectiveTransformWorker perspectiveWorker(device, config.transformedCenter(), config.aX(), config.aY(), config.cameraPos().z(), updater2); perspectiveWorker.run(); } else if (config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) { QTransform T = QTransform::fromTranslate(config.transformedCenter().x(), config.transformedCenter().y()); KisPerspectiveTransformWorker perspectiveWorker(device, T.inverted() * config.flattenedPerspectiveTransform() * T, updater2); perspectiveWorker.run(); } } } QRect KisTransformUtils::needRect(const ToolTransformArgs &config, const QRect &rc, const QRect &srcBounds) { QRect result = rc; if (config.mode() == ToolTransformArgs::WARP) { KisWarpTransformWorker worker(config.warpType(), 0, config.origPoints(), config.transfPoints(), config.alpha(), 0); result = worker.approxNeedRect(rc, srcBounds); } else if (config.mode() == ToolTransformArgs::CAGE) { KisCageTransformWorker worker(0, config.origPoints(), 0, config.pixelPrecision()); worker.setTransformedCage(config.transfPoints()); result = worker.approxNeedRect(rc, srcBounds); } else if (config.mode() == ToolTransformArgs::LIQUIFY) { result = config.liquifyWorker() ? config.liquifyWorker()->approxNeedRect(rc, srcBounds) : rc; } else { KIS_ASSERT_RECOVER_NOOP(0 && "this works for non-affine transformations only!"); } return result; } QRect KisTransformUtils::changeRect(const ToolTransformArgs &config, const QRect &rc) { QRect result = rc; if (config.mode() == ToolTransformArgs::WARP) { KisWarpTransformWorker worker(config.warpType(), 0, config.origPoints(), config.transfPoints(), config.alpha(), 0); result = worker.approxChangeRect(rc); } else if (config.mode() == ToolTransformArgs::CAGE) { KisCageTransformWorker worker(0, config.origPoints(), 0, config.pixelPrecision()); worker.setTransformedCage(config.transfPoints()); result = worker.approxChangeRect(rc); } else if (config.mode() == ToolTransformArgs::LIQUIFY) { result = config.liquifyWorker() ? config.liquifyWorker()->approxChangeRect(rc) : rc; } else { KIS_ASSERT_RECOVER_NOOP(0 && "this works for non-affine transformations only!"); } return result; } KisTransformUtils::AnchorHolder::AnchorHolder(bool enabled, ToolTransformArgs *config) : m_enabled(enabled), m_config(config) { if (!m_enabled) return; m_staticPoint = m_config->originalCenter() + m_config->rotationCenterOffset(); const KisTransformUtils::MatricesPack m(*m_config); m_oldStaticPointInView = m.finalTransform().map(m_staticPoint); } KisTransformUtils::AnchorHolder::~AnchorHolder() { if (!m_enabled) return; const KisTransformUtils::MatricesPack m(*m_config); const QPointF newStaticPointInView = m.finalTransform().map(m_staticPoint); const QPointF diff = m_oldStaticPointInView - newStaticPointInView; m_config->setTransformedCenter(m_config->transformedCenter() + diff); } void KisTransformUtils::setDefaultWarpPoints(int pointsPerLine, const TransformTransactionProperties *transaction, ToolTransformArgs *config) { static const int DEFAULT_POINTS_PER_LINE = 3; if (pointsPerLine < 0) { pointsPerLine = DEFAULT_POINTS_PER_LINE; } int nbPoints = pointsPerLine * pointsPerLine; QVector origPoints(nbPoints); QVector transfPoints(nbPoints); qreal gridSpaceX, gridSpaceY; if (nbPoints == 1) { //there is actually no grid origPoints[0] = transaction->originalCenterGeometric(); transfPoints[0] = transaction->originalCenterGeometric(); } else if (nbPoints > 1) { gridSpaceX = transaction->originalRect().width() / (pointsPerLine - 1); gridSpaceY = transaction->originalRect().height() / (pointsPerLine - 1); double y = transaction->originalRect().top(); for (int i = 0; i < pointsPerLine; ++i) { double x = transaction->originalRect().left(); for (int j = 0 ; j < pointsPerLine; ++j) { origPoints[i * pointsPerLine + j] = QPointF(x, y); transfPoints[i * pointsPerLine + j] = QPointF(x, y); x += gridSpaceX; } y += gridSpaceY; } } config->setDefaultPoints(nbPoints > 0); config->setPoints(origPoints, transfPoints); } ToolTransformArgs KisTransformUtils::resetArgsForMode(ToolTransformArgs::TransformMode mode, const QString &filterId, const TransformTransactionProperties &transaction) { ToolTransformArgs args; args.setOriginalCenter(transaction.originalCenterGeometric()); args.setTransformedCenter(transaction.originalCenterGeometric()); args.setFilterId(filterId); if (mode == ToolTransformArgs::FREE_TRANSFORM) { args.setMode(ToolTransformArgs::FREE_TRANSFORM); } else if (mode == ToolTransformArgs::WARP) { args.setMode(ToolTransformArgs::WARP); KisTransformUtils::setDefaultWarpPoints(-1, &transaction, &args); args.setEditingTransformPoints(false); } else if (mode == ToolTransformArgs::CAGE) { args.setMode(ToolTransformArgs::CAGE); args.setEditingTransformPoints(true); } else if (mode == ToolTransformArgs::LIQUIFY) { args.setMode(ToolTransformArgs::LIQUIFY); const QRect srcRect = transaction.originalRect().toAlignedRect(); if (!srcRect.isEmpty()) { args.initLiquifyTransformMode(transaction.originalRect().toAlignedRect()); } } else if (mode == ToolTransformArgs::PERSPECTIVE_4POINT) { args.setMode(ToolTransformArgs::PERSPECTIVE_4POINT); } return args; } diff --git a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp index 42d405feae..71622304d9 100644 --- a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp +++ b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp @@ -1,717 +1,713 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "transform_stroke_strategy.h" #include #include "kundo2commandextradata.h" #include "kis_node_progress_proxy.h" #include #include #include #include #include #include #include #include #include "kis_transform_mask_adapter.h" #include "kis_transform_utils.h" #include "kis_abstract_projection_plane.h" #include "kis_recalculate_transform_mask_job.h" #include "kis_projection_leaf.h" #include "kis_modify_transform_mask_command.h" #include "kis_sequential_iterator.h" #include "kis_selection_mask.h" #include "kis_image_config.h" #include "kis_layer_utils.h" #include #include #include "transform_transaction_properties.h" #include "krita_container_utils.h" #include "commands_new/kis_saved_commands.h" #include "kis_command_ids.h" #include "KisRunnableStrokeJobUtils.h" #include "commands_new/KisHoldUIUpdatesCommand.h" #include "KisDecoratedNodeInterface.h" TransformStrokeStrategy::TransformStrokeStrategy(ToolTransformArgs::TransformMode mode, bool workRecursively, const QString &filterId, bool forceReset, KisNodeSP rootNode, KisSelectionSP selection, KisStrokeUndoFacade *undoFacade, KisUpdatesFacade *updatesFacade) : KisStrokeStrategyUndoCommandBased(kundo2_i18n("Transform"), false, undoFacade), m_updatesFacade(updatesFacade), m_mode(mode), m_workRecursively(workRecursively), m_filterId(filterId), m_forceReset(forceReset), m_selection(selection) { KIS_SAFE_ASSERT_RECOVER_NOOP(!selection || !dynamic_cast(rootNode.data())); m_rootNode = rootNode; setMacroId(KisCommandUtils::TransformToolId); } TransformStrokeStrategy::~TransformStrokeStrategy() { } KisPaintDeviceSP TransformStrokeStrategy::createDeviceCache(KisPaintDeviceSP dev) { KisPaintDeviceSP cache; if (m_selection) { QRect srcRect = m_selection->selectedExactRect(); cache = dev->createCompositionSourceDevice(); KisPainter gc(cache); gc.setSelection(m_selection); gc.bitBlt(srcRect.topLeft(), dev, srcRect); } else { cache = dev->createCompositionSourceDevice(dev); } return cache; } bool TransformStrokeStrategy::haveDeviceInCache(KisPaintDeviceSP src) { QMutexLocker l(&m_devicesCacheMutex); return m_devicesCacheHash.contains(src.data()); } void TransformStrokeStrategy::putDeviceCache(KisPaintDeviceSP src, KisPaintDeviceSP cache) { QMutexLocker l(&m_devicesCacheMutex); m_devicesCacheHash.insert(src.data(), cache); } KisPaintDeviceSP TransformStrokeStrategy::getDeviceCache(KisPaintDeviceSP src) { QMutexLocker l(&m_devicesCacheMutex); KisPaintDeviceSP cache = m_devicesCacheHash.value(src.data()); if (!cache) { warnKrita << "WARNING: Transform Stroke: the device is absent in cache!"; } return cache; } bool TransformStrokeStrategy::checkBelongsToSelection(KisPaintDeviceSP device) const { return m_selection && (device == m_selection->pixelSelection().data() || device == m_selection->projection().data()); } void TransformStrokeStrategy::doStrokeCallback(KisStrokeJobData *data) { TransformData *td = dynamic_cast(data); ClearSelectionData *csd = dynamic_cast(data); PreparePreviewData *ppd = dynamic_cast(data); TransformAllData *runAllData = dynamic_cast(data); if (runAllData) { // here we only save the passed args, actual // transformation will be performed during // finish job m_savedTransformArgs = runAllData->config; } else if (ppd) { KisNodeSP rootNode = m_rootNode; KisNodeList processedNodes = m_processedNodes; KisPaintDeviceSP previewDevice; if (rootNode->childCount() || !rootNode->paintDevice()) { if (KisTransformMask* tmask = dynamic_cast(rootNode.data())) { previewDevice = createDeviceCache(tmask->buildPreviewDevice()); KIS_SAFE_ASSERT_RECOVER(!m_selection) { m_selection = 0; } } else if (KisGroupLayer *group = dynamic_cast(rootNode.data())) { const QRect bounds = group->image()->bounds(); KisImageSP clonedImage = new KisImage(0, bounds.width(), bounds.height(), group->colorSpace(), "transformed_image"); KisGroupLayerSP clonedGroup = dynamic_cast(group->clone().data()); // In case the group is pass-through, it needs to be disabled for the preview, // otherwise it will crash (no parent for a preview leaf). // Also it needs to be done before setting the root layer for clonedImage. // Result: preview for pass-through group is the same as for standard group // (i.e. filter layers in the group won't affect the layer stack for a moment). clonedGroup->setPassThroughMode(false); clonedImage->setRootLayer(clonedGroup); QQueue linearizedSrcNodes; KisLayerUtils::recursiveApplyNodes(rootNode, [&linearizedSrcNodes] (KisNodeSP node) { linearizedSrcNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(KisNodeSP(clonedGroup), [&linearizedSrcNodes, processedNodes] (KisNodeSP node) { KisNodeSP srcNode = linearizedSrcNodes.dequeue(); if (!processedNodes.contains(srcNode)) { node->setVisible(false); } }); clonedImage->refreshGraph(); KisLayerUtils::refreshHiddenAreaAsync(clonedImage, clonedGroup, clonedImage->bounds()); KisLayerUtils::forceAllDelayedNodesUpdate(clonedGroup); clonedImage->waitForDone(); previewDevice = createDeviceCache(clonedImage->projection()); previewDevice->setDefaultBounds(group->projection()->defaultBounds()); // we delete the cloned image in GUI thread to ensure // no signals are still pending makeKisDeleteLaterWrapper(clonedImage)->deleteLater(); } else { rootNode->projectionLeaf()->explicitlyRegeneratePassThroughProjection(); previewDevice = createDeviceCache(rootNode->projection()); } } else { KisPaintDeviceSP cacheDevice = createDeviceCache(rootNode->paintDevice()); if (dynamic_cast(rootNode.data())) { KIS_SAFE_ASSERT_RECOVER (cacheDevice->colorSpace()->colorModelId() == GrayAColorModelID && cacheDevice->colorSpace()->colorDepthId() == Integer8BitsColorDepthID) { cacheDevice->convertTo(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id())); } previewDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); const QRect srcRect = cacheDevice->exactBounds(); KisSequentialConstIterator srcIt(cacheDevice, srcRect); KisSequentialIterator dstIt(previewDevice, srcRect); const int pixelSize = previewDevice->colorSpace()->pixelSize(); KisImageConfig cfg(true); KoColor pixel(cfg.selectionOverlayMaskColor(), previewDevice->colorSpace()); const qreal coeff = 1.0 / 255.0; const qreal baseOpacity = 0.5; while (srcIt.nextPixel() && dstIt.nextPixel()) { qreal gray = srcIt.rawDataConst()[0]; qreal alpha = srcIt.rawDataConst()[1]; pixel.setOpacity(quint8(gray * alpha * baseOpacity * coeff)); memcpy(dstIt.rawData(), pixel.data(), pixelSize); } } else { previewDevice = cacheDevice; } putDeviceCache(rootNode->paintDevice(), cacheDevice); } emit sigPreviewDeviceReady(previewDevice); - } else if(td) { + } + else if (td) { if (td->destination == TransformData::PAINT_DEVICE) { QRect oldExtent = td->node->extent(); KisPaintDeviceSP device = td->node->paintDevice(); if (device && !checkBelongsToSelection(device)) { KisPaintDeviceSP cachedPortion = getDeviceCache(device); Q_ASSERT(cachedPortion); KisTransaction transaction(device); KisProcessingVisitor::ProgressHelper helper(td->node); transformAndMergeDevice(td->config, cachedPortion, device, &helper); runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); td->node->setDirty(oldExtent | td->node->extent()); } else if (KisExternalLayer *extLayer = dynamic_cast(td->node.data())) { if (td->config.mode() == ToolTransformArgs::FREE_TRANSFORM || (td->config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT && extLayer->supportsPerspectiveTransform())) { QVector3D transformedCenter; KisTransformWorker w = KisTransformUtils::createTransformWorker(td->config, 0, 0, &transformedCenter); QTransform t = w.transform(); runAndSaveCommand(KUndo2CommandSP(extLayer->transform(t)), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); } } else if (KisTransformMask *transformMask = dynamic_cast(td->node.data())) { runAndSaveCommand(KUndo2CommandSP( new KisModifyTransformMaskCommand(transformMask, KisTransformMaskParamsInterfaceSP( new KisTransformMaskAdapter(td->config)))), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); } } else if (m_selection) { /** * We use usual transaction here, because we cannot calsulate * transformation for perspective and warp workers. */ KisTransaction transaction(m_selection->pixelSelection()); KisProcessingVisitor::ProgressHelper helper(td->node); KisTransformUtils::transformDevice(td->config, m_selection->pixelSelection(), &helper); runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); } } else if (csd) { KisPaintDeviceSP device = csd->node->paintDevice(); if (device && !checkBelongsToSelection(device)) { if (!haveDeviceInCache(device)) { putDeviceCache(device, createDeviceCache(device)); } clearSelection(device); /** * Selection masks might have an overlay enabled, we should disable that */ if (KisSelectionMask *mask = dynamic_cast(csd->node.data())) { KisSelectionSP selection = mask->selection(); if (selection) { selection->setVisible(false); m_deactivatedSelections.append(selection); mask->setDirty(); } } } else if (KisExternalLayer *externalLayer = dynamic_cast(csd->node.data())) { externalLayer->projectionLeaf()->setTemporaryHiddenFromRendering(true); externalLayer->setDirty(); m_hiddenProjectionLeaves.append(csd->node); } else if (KisTransformMask *transformMask = dynamic_cast(csd->node.data())) { runAndSaveCommand(KUndo2CommandSP( new KisModifyTransformMaskCommand(transformMask, KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(true)))), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } } else { KisStrokeStrategyUndoCommandBased::doStrokeCallback(data); } } void TransformStrokeStrategy::clearSelection(KisPaintDeviceSP device) { KisTransaction transaction(device); if (m_selection) { device->clearSelection(m_selection); } else { QRect oldExtent = device->extent(); device->clear(); device->setDirty(oldExtent); } runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } void TransformStrokeStrategy::transformAndMergeDevice(const ToolTransformArgs &config, KisPaintDeviceSP src, KisPaintDeviceSP dst, KisProcessingVisitor::ProgressHelper *helper) { KoUpdaterPtr mergeUpdater = src != dst ? helper->updater() : 0; KisTransformUtils::transformDevice(config, src, helper); if (src != dst) { QRect mergeRect = src->extent(); KisPainter painter(dst); painter.setProgress(mergeUpdater); painter.bitBlt(mergeRect.topLeft(), src, mergeRect); painter.end(); } } struct TransformExtraData : public KUndo2CommandExtraData { ToolTransformArgs savedTransformArgs; KisNodeSP rootNode; KisNodeList transformedNodes; KUndo2CommandExtraData* clone() const override { return new TransformExtraData(*this); } }; void TransformStrokeStrategy::postProcessToplevelCommand(KUndo2Command *command) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_savedTransformArgs); TransformExtraData *data = new TransformExtraData(); data->savedTransformArgs = *m_savedTransformArgs; data->rootNode = m_rootNode; data->transformedNodes = m_processedNodes; command->setExtraData(data); KisSavedMacroCommand *macroCommand = dynamic_cast(command); KIS_SAFE_ASSERT_RECOVER_NOOP(macroCommand); if (m_overriddenCommand && macroCommand) { macroCommand->setOverrideInfo(m_overriddenCommand, m_skippedWhileMergeCommands); } KisStrokeStrategyUndoCommandBased::postProcessToplevelCommand(command); } bool TransformStrokeStrategy::fetchArgsFromCommand(const KUndo2Command *command, ToolTransformArgs *args, KisNodeSP *rootNode, KisNodeList *transformedNodes) { const TransformExtraData *data = dynamic_cast(command->extraData()); if (data) { *args = data->savedTransformArgs; *rootNode = data->rootNode; *transformedNodes = data->transformedNodes; } return bool(data); } QList TransformStrokeStrategy::fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive) { QList result; auto fetchFunc = [&result, mode, root] (KisNodeSP node) { if (node->isEditable(node == root) && (!node->inherits("KisShapeLayer") || mode == ToolTransformArgs::FREE_TRANSFORM) && !node->inherits("KisFileLayer") && (!node->inherits("KisTransformMask") || node == root)) { result << node; } }; if (recursive) { KisLayerUtils::recursiveApplyNodes(root, fetchFunc); } else { fetchFunc(root); } return result; } bool TransformStrokeStrategy::tryInitArgsFromNode(KisNodeSP node, ToolTransformArgs *args) { bool result = false; if (KisTransformMaskSP mask = dynamic_cast(node.data())) { KisTransformMaskParamsInterfaceSP savedParams = mask->transformParams(); KisTransformMaskAdapter *adapter = dynamic_cast(savedParams.data()); if (adapter) { *args = adapter->transformArgs(); result = true; } } return result; } bool TransformStrokeStrategy::tryFetchArgsFromCommandAndUndo(ToolTransformArgs *outArgs, ToolTransformArgs::TransformMode mode, KisNodeSP currentNode, KisNodeList selectedNodes, QVector *undoJobs) { bool result = false; const KUndo2Command *lastCommand = undoFacade()->lastExecutedCommand(); KisNodeSP oldRootNode; KisNodeList oldTransformedNodes; ToolTransformArgs args; if (lastCommand && TransformStrokeStrategy::fetchArgsFromCommand(lastCommand, &args, &oldRootNode, &oldTransformedNodes) && args.mode() == mode && oldRootNode == currentNode) { if (KritaUtils::compareListsUnordered(oldTransformedNodes, selectedNodes)) { args.saveContinuedState(); *outArgs = args; const KisSavedMacroCommand *command = dynamic_cast(lastCommand); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(command, false); // the jobs are fetched as !shouldGoToHistory, // so there is no need to put them into // m_skippedWhileMergeCommands command->getCommandExecutionJobs(undoJobs, true, false); m_overriddenCommand = command; result = true; } } return result; } void TransformStrokeStrategy::initStrokeCallback() { KisStrokeStrategyUndoCommandBased::initStrokeCallback(); if (m_selection) { m_selection->setVisible(false); m_deactivatedSelections.append(m_selection); } KisSelectionMaskSP overlaySelectionMask = dynamic_cast(m_rootNode->graphListener()->graphOverlayNode()); if (overlaySelectionMask) { overlaySelectionMask->setDecorationsVisible(false); m_deactivatedOverlaySelectionMask = overlaySelectionMask; } ToolTransformArgs initialTransformArgs; m_processedNodes = fetchNodesList(m_mode, m_rootNode, m_workRecursively); bool argsAreInitialized = false; QVector lastCommandUndoJobs; if (!m_forceReset && tryFetchArgsFromCommandAndUndo(&initialTransformArgs, m_mode, m_rootNode, m_processedNodes, &lastCommandUndoJobs)) { argsAreInitialized = true; } else if (!m_forceReset && tryInitArgsFromNode(m_rootNode, &initialTransformArgs)) { argsAreInitialized = true; } QVector extraInitJobs; extraInitJobs << new Data(new KisHoldUIUpdatesCommand(m_updatesFacade, KisCommandUtils::FlipFlopCommand::INITIALIZING), false, KisStrokeJobData::BARRIER); extraInitJobs << lastCommandUndoJobs; KritaUtils::addJobSequential(extraInitJobs, [this]() { /** * We must request shape layers to rerender areas outside image bounds */ KisLayerUtils::forceAllHiddenOriginalsUpdate(m_rootNode); }); KritaUtils::addJobBarrier(extraInitJobs, [this]() { /** * We must ensure that the currently selected subtree * has finished all its updates. */ KisLayerUtils::forceAllDelayedNodesUpdate(m_rootNode); }); /// Disable all decorated nodes to generate outline /// and preview correctly. We will enable them back /// as soon as preview generation is finished. KritaUtils::addJobBarrier(extraInitJobs, [this]() { Q_FOREACH (KisNodeSP node, m_processedNodes) { KisDecoratedNodeInterface *decoratedNode = dynamic_cast(node.data()); if (decoratedNode && decoratedNode->decorationsVisible()) { decoratedNode->setDecorationsVisible(false); m_disabledDecoratedNodes << decoratedNode; } } }); KritaUtils::addJobBarrier(extraInitJobs, [this, initialTransformArgs, argsAreInitialized]() mutable { QRect srcRect; if (m_selection) { srcRect = m_selection->selectedExactRect(); } else { srcRect = QRect(); Q_FOREACH (KisNodeSP node, m_processedNodes) { // group layers may have a projection of layers // that are locked and will not be transformed if (node->inherits("KisGroupLayer")) continue; if (const KisTransformMask *mask = dynamic_cast(node.data())) { srcRect |= mask->sourceDataBounds(); } else if (const KisSelectionMask *mask = dynamic_cast(node.data())) { srcRect |= mask->selection()->selectedExactRect(); } else { srcRect |= node->exactBounds(); } } } TransformTransactionProperties transaction(srcRect, &initialTransformArgs, m_rootNode, m_processedNodes); if (!argsAreInitialized) { initialTransformArgs = KisTransformUtils::resetArgsForMode(m_mode, m_filterId, transaction); } this->m_initialTransformArgs = initialTransformArgs; emit this->sigTransactionGenerated(transaction, initialTransformArgs, this); }); extraInitJobs << new PreparePreviewData(); Q_FOREACH (KisNodeSP node, m_processedNodes) { extraInitJobs << new ClearSelectionData(node); } /// recover back visibility of decorated nodes KritaUtils::addJobBarrier(extraInitJobs, [this]() { Q_FOREACH (KisDecoratedNodeInterface *decoratedNode, m_disabledDecoratedNodes) { decoratedNode->setDecorationsVisible(true); } m_disabledDecoratedNodes.clear(); }); extraInitJobs << new Data(toQShared(new KisHoldUIUpdatesCommand(m_updatesFacade, KisCommandUtils::FlipFlopCommand::FINALIZING)), false, KisStrokeJobData::BARRIER); if (!lastCommandUndoJobs.isEmpty()) { KIS_SAFE_ASSERT_RECOVER_NOOP(m_overriddenCommand); for (auto it = extraInitJobs.begin(); it != extraInitJobs.end(); ++it) { (*it)->setCancellable(false); } } addMutatedJobs(extraInitJobs); } void TransformStrokeStrategy::finishStrokeImpl(bool applyTransform, const ToolTransformArgs &args) { /** * Since our finishStrokeCallback() initiates new jobs, * cancellation request may come even after * finishStrokeCallback() (cancellations may be called * until there are no jobs left in the stroke's queue). * * Therefore we should check for double-entry here and * make sure the finilizing jobs are no cancellable. */ if (m_finalizingActionsStarted) return; m_finalizingActionsStarted = true; QVector mutatedJobs; if (applyTransform) { + m_savedTransformArgs = args; + Q_FOREACH (KisNodeSP node, m_processedNodes) { mutatedJobs << new TransformData(TransformData::PAINT_DEVICE, args, node); } mutatedJobs << new TransformData(TransformData::SELECTION, args, m_rootNode); } KritaUtils::addJobBarrier(mutatedJobs, [this, applyTransform]() { Q_FOREACH (KisSelectionSP selection, m_deactivatedSelections) { selection->setVisible(true); } Q_FOREACH (KisNodeSP node, m_hiddenProjectionLeaves) { node->projectionLeaf()->setTemporaryHiddenFromRendering(false); } if (m_deactivatedOverlaySelectionMask) { m_deactivatedOverlaySelectionMask->selection()->setVisible(true); m_deactivatedOverlaySelectionMask->setDirty(); } if (applyTransform) { KisStrokeStrategyUndoCommandBased::finishStrokeCallback(); } else { KisStrokeStrategyUndoCommandBased::cancelStrokeCallback(); } }); for (auto it = mutatedJobs.begin(); it != mutatedJobs.end(); ++it) { (*it)->setCancellable(false); } addMutatedJobs(mutatedJobs); } void TransformStrokeStrategy::finishStrokeCallback() { if (!m_savedTransformArgs || m_savedTransformArgs->isIdentity()) { cancelStrokeCallback(); return; } finishStrokeImpl(true, *m_savedTransformArgs); } void TransformStrokeStrategy::cancelStrokeCallback() { - const bool shouldRecoverSavedInitialState = - !m_initialTransformArgs.isIdentity(); - - if (shouldRecoverSavedInitialState) { - m_savedTransformArgs = m_initialTransformArgs; - } - - finishStrokeImpl(shouldRecoverSavedInitialState, *m_savedTransformArgs); + finishStrokeImpl(!m_initialTransformArgs.isIdentity(), m_initialTransformArgs); } diff --git a/plugins/tools/tool_transform2/tool_transform_args.cc b/plugins/tools/tool_transform2/tool_transform_args.cc index ce49669134..a013d3a4c4 100644 --- a/plugins/tools/tool_transform2/tool_transform_args.cc +++ b/plugins/tools/tool_transform2/tool_transform_args.cc @@ -1,510 +1,492 @@ /* * tool_transform_args.h - part of Krita * * 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 "tool_transform_args.h" #include #include #include #include #include "kis_liquify_transform_worker.h" #include "kis_dom_utils.h" ToolTransformArgs::ToolTransformArgs() - : m_mode(FREE_TRANSFORM) - , m_defaultPoints(true) - , m_origPoints {QVector()} - , m_transfPoints {QVector()} - , m_warpType(KisWarpTransformWorker::RIGID_TRANSFORM) - , m_alpha(1.0) - , m_transformedCenter(QPointF(0, 0)) - , m_originalCenter(QPointF(0, 0)) - , m_rotationCenterOffset(QPointF(0, 0)) - , m_aX(0) - , m_aY(0) - , m_aZ(0) - , m_scaleX(1.0) - , m_scaleY(1.0) - , m_shearX(0.0) - , m_shearY(0.0) - , m_liquifyProperties(new KisLiquifyProperties()) - , m_pixelPrecision(8) - , m_previewPixelPrecision(16) + : m_liquifyProperties(new KisLiquifyProperties()) { KConfigGroup configGroup = KSharedConfig::openConfig()->group("KisToolTransform"); QString savedFilterId = configGroup.readEntry("filterId", "Bicubic"); setFilterId(savedFilterId); m_transformAroundRotationCenter = configGroup.readEntry("transformAroundRotationCenter", "0").toInt(); } void ToolTransformArgs::setFilterId(const QString &id) { m_filter = KisFilterStrategyRegistry::instance()->value(id); if (m_filter) { KConfigGroup configGroup = KSharedConfig::openConfig()->group("KisToolTransform"); configGroup.writeEntry("filterId", id); } } void ToolTransformArgs::setTransformAroundRotationCenter(bool value) { m_transformAroundRotationCenter = value; KConfigGroup configGroup = KSharedConfig::openConfig()->group("KisToolTransform"); configGroup.writeEntry("transformAroundRotationCenter", int(value)); } void ToolTransformArgs::init(const ToolTransformArgs& args) { m_mode = args.mode(); m_transformedCenter = args.transformedCenter(); m_originalCenter = args.originalCenter(); m_rotationCenterOffset = args.rotationCenterOffset(); m_transformAroundRotationCenter = args.transformAroundRotationCenter(); m_cameraPos = args.m_cameraPos; m_aX = args.aX(); m_aY = args.aY(); m_aZ = args.aZ(); m_scaleX = args.scaleX(); m_scaleY = args.scaleY(); m_shearX = args.shearX(); m_shearY = args.shearY(); m_origPoints = args.origPoints(); //it's a copy m_transfPoints = args.transfPoints(); m_warpType = args.warpType(); m_alpha = args.alpha(); m_defaultPoints = args.defaultPoints(); m_keepAspectRatio = args.keepAspectRatio(); m_filter = args.m_filter; m_flattenedPerspectiveTransform = args.m_flattenedPerspectiveTransform; m_editTransformPoints = args.m_editTransformPoints; m_pixelPrecision = args.pixelPrecision(); m_previewPixelPrecision = args.previewPixelPrecision(); if (args.m_liquifyWorker) { m_liquifyWorker.reset(new KisLiquifyTransformWorker(*args.m_liquifyWorker.data())); } m_continuedTransformation.reset(args.m_continuedTransformation ? new ToolTransformArgs(*args.m_continuedTransformation) : 0); } void ToolTransformArgs::clear() { m_origPoints.clear(); m_transfPoints.clear(); } ToolTransformArgs::ToolTransformArgs(const ToolTransformArgs& args) : m_liquifyProperties(new KisLiquifyProperties(*args.m_liquifyProperties.data())) { init(args); } KisToolChangesTrackerData *ToolTransformArgs::clone() const { return new ToolTransformArgs(*this); } ToolTransformArgs& ToolTransformArgs::operator=(const ToolTransformArgs& args) { clear(); m_liquifyProperties.reset(new KisLiquifyProperties(*args.m_liquifyProperties.data())); init(args); return *this; } bool ToolTransformArgs::operator==(const ToolTransformArgs& other) const { return m_mode == other.m_mode && m_defaultPoints == other.m_defaultPoints && m_origPoints == other.m_origPoints && m_transfPoints == other.m_transfPoints && m_warpType == other.m_warpType && m_alpha == other.m_alpha && m_transformedCenter == other.m_transformedCenter && m_originalCenter == other.m_originalCenter && m_rotationCenterOffset == other.m_rotationCenterOffset && m_transformAroundRotationCenter == other.m_transformAroundRotationCenter && m_aX == other.m_aX && m_aY == other.m_aY && m_aZ == other.m_aZ && m_cameraPos == other.m_cameraPos && m_scaleX == other.m_scaleX && m_scaleY == other.m_scaleY && m_shearX == other.m_shearX && m_shearY == other.m_shearY && m_keepAspectRatio == other.m_keepAspectRatio && m_flattenedPerspectiveTransform == other.m_flattenedPerspectiveTransform && m_editTransformPoints == other.m_editTransformPoints && (m_liquifyProperties == other.m_liquifyProperties || *m_liquifyProperties == *other.m_liquifyProperties) && // pointer types ((m_filter && other.m_filter && m_filter->id() == other.m_filter->id()) || m_filter == other.m_filter) && ((m_liquifyWorker && other.m_liquifyWorker && *m_liquifyWorker == *other.m_liquifyWorker) || m_liquifyWorker == other.m_liquifyWorker) && m_pixelPrecision == other.m_pixelPrecision && m_previewPixelPrecision == other.m_previewPixelPrecision; } bool ToolTransformArgs::isSameMode(const ToolTransformArgs& other) const { if (m_mode != other.m_mode) return false; bool result = true; if (m_mode == FREE_TRANSFORM) { result &= m_transformedCenter == other.m_transformedCenter; result &= m_originalCenter == other.m_originalCenter; result &= m_scaleX == other.m_scaleX; result &= m_scaleY == other.m_scaleY; result &= m_shearX == other.m_shearX; result &= m_shearY == other.m_shearY; result &= m_aX == other.m_aX; result &= m_aY == other.m_aY; result &= m_aZ == other.m_aZ; } else if (m_mode == PERSPECTIVE_4POINT) { result &= m_transformedCenter == other.m_transformedCenter; result &= m_originalCenter == other.m_originalCenter; result &= m_scaleX == other.m_scaleX; result &= m_scaleY == other.m_scaleY; result &= m_shearX == other.m_shearX; result &= m_shearY == other.m_shearY; result &= m_flattenedPerspectiveTransform == other.m_flattenedPerspectiveTransform; } else if(m_mode == WARP || m_mode == CAGE) { result &= m_origPoints == other.m_origPoints; result &= m_transfPoints == other.m_transfPoints; } else if (m_mode == LIQUIFY) { result &= m_liquifyProperties && (m_liquifyProperties == other.m_liquifyProperties || *m_liquifyProperties == *other.m_liquifyProperties); result &= (m_liquifyWorker && other.m_liquifyWorker && *m_liquifyWorker == *other.m_liquifyWorker) || m_liquifyWorker == other.m_liquifyWorker; } else { KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "unknown transform mode"); } return result; } ToolTransformArgs::ToolTransformArgs(TransformMode mode, QPointF transformedCenter, QPointF originalCenter, QPointF rotationCenterOffset, bool transformAroundRotationCenter, double aX, double aY, double aZ, double scaleX, double scaleY, double shearX, double shearY, KisWarpTransformWorker::WarpType warpType, double alpha, bool defaultPoints, const QString &filterId, int pixelPrecision, int previewPixelPrecision) : m_mode(mode) , m_defaultPoints(defaultPoints) , m_origPoints {QVector()} , m_transfPoints {QVector()} , m_warpType(warpType) , m_alpha(alpha) , m_transformedCenter(transformedCenter) , m_originalCenter(originalCenter) , m_rotationCenterOffset(rotationCenterOffset) , m_transformAroundRotationCenter(transformAroundRotationCenter) , m_aX(aX) , m_aY(aY) , m_aZ(aZ) , m_scaleX(scaleX) , m_scaleY(scaleY) , m_shearX(shearX) , m_shearY(shearY) , m_liquifyProperties(new KisLiquifyProperties()) , m_pixelPrecision(pixelPrecision) , m_previewPixelPrecision(previewPixelPrecision) { setFilterId(filterId); } ToolTransformArgs::~ToolTransformArgs() { clear(); } void ToolTransformArgs::translate(const QPointF &offset) { if (m_mode == FREE_TRANSFORM || m_mode == PERSPECTIVE_4POINT) { m_originalCenter += offset; m_rotationCenterOffset += offset; m_transformedCenter += offset; } else if(m_mode == WARP || m_mode == CAGE) { for (auto &pt : m_origPoints) { pt += offset; } for (auto &pt : m_transfPoints) { pt += offset; } } else if (m_mode == LIQUIFY) { KIS_ASSERT_RECOVER_RETURN(m_liquifyWorker); m_liquifyWorker->translate(offset); } else { KIS_ASSERT_RECOVER_NOOP(0 && "unknown transform mode"); } } bool ToolTransformArgs::isIdentity() const { if (m_mode == FREE_TRANSFORM) { return (m_transformedCenter == m_originalCenter && m_scaleX == 1 && m_scaleY == 1 && m_shearX == 0 && m_shearY == 0 && m_aX == 0 && m_aY == 0 && m_aZ == 0); } else if (m_mode == PERSPECTIVE_4POINT) { return (m_transformedCenter == m_originalCenter && m_scaleX == 1 && m_scaleY == 1 && m_shearX == 0 && m_shearY == 0 && m_flattenedPerspectiveTransform.isIdentity()); } else if(m_mode == WARP || m_mode == CAGE) { for (int i = 0; i < m_origPoints.size(); ++i) if (m_origPoints[i] != m_transfPoints[i]) return false; return true; } else if (m_mode == LIQUIFY) { // Not implemented! return false; } else { KIS_ASSERT_RECOVER_NOOP(0 && "unknown transform mode"); return true; } } void ToolTransformArgs::initLiquifyTransformMode(const QRect &srcRect) { m_liquifyWorker.reset(new KisLiquifyTransformWorker(srcRect, 0, 8)); m_liquifyProperties->loadAndResetMode(); } void ToolTransformArgs::saveLiquifyTransformMode() const { m_liquifyProperties->saveMode(); } void ToolTransformArgs::toXML(QDomElement *e) const { e->setAttribute("mode", (int) m_mode); QDomDocument doc = e->ownerDocument(); if (m_mode == FREE_TRANSFORM || m_mode == PERSPECTIVE_4POINT) { QDomElement freeEl = doc.createElement("free_transform"); e->appendChild(freeEl); KisDomUtils::saveValue(&freeEl, "transformedCenter", m_transformedCenter); KisDomUtils::saveValue(&freeEl, "originalCenter", m_originalCenter); KisDomUtils::saveValue(&freeEl, "rotationCenterOffset", m_rotationCenterOffset); KisDomUtils::saveValue(&freeEl, "transformAroundRotationCenter", m_transformAroundRotationCenter); KisDomUtils::saveValue(&freeEl, "aX", m_aX); KisDomUtils::saveValue(&freeEl, "aY", m_aY); KisDomUtils::saveValue(&freeEl, "aZ", m_aZ); KisDomUtils::saveValue(&freeEl, "cameraPos", m_cameraPos); KisDomUtils::saveValue(&freeEl, "scaleX", m_scaleX); KisDomUtils::saveValue(&freeEl, "scaleY", m_scaleY); KisDomUtils::saveValue(&freeEl, "shearX", m_shearX); KisDomUtils::saveValue(&freeEl, "shearY", m_shearY); KisDomUtils::saveValue(&freeEl, "keepAspectRatio", m_keepAspectRatio); KisDomUtils::saveValue(&freeEl, "flattenedPerspectiveTransform", m_flattenedPerspectiveTransform); KisDomUtils::saveValue(&freeEl, "filterId", m_filter->id()); } else if (m_mode == WARP || m_mode == CAGE) { QDomElement warpEl = doc.createElement("warp_transform"); e->appendChild(warpEl); KisDomUtils::saveValue(&warpEl, "defaultPoints", m_defaultPoints); KisDomUtils::saveValue(&warpEl, "originalPoints", m_origPoints); KisDomUtils::saveValue(&warpEl, "transformedPoints", m_transfPoints); KisDomUtils::saveValue(&warpEl, "warpType", (int)m_warpType); // limited! KisDomUtils::saveValue(&warpEl, "alpha", m_alpha); if(m_mode == CAGE){ KisDomUtils::saveValue(&warpEl,"pixelPrecision",m_pixelPrecision); KisDomUtils::saveValue(&warpEl,"previewPixelPrecision",m_previewPixelPrecision); } } else if (m_mode == LIQUIFY) { QDomElement liqEl = doc.createElement("liquify_transform"); e->appendChild(liqEl); m_liquifyProperties->toXML(&liqEl); m_liquifyWorker->toXML(&liqEl); } else { KIS_ASSERT_RECOVER_RETURN(0 && "Unknown transform mode"); } // m_editTransformPoints should not be saved since it is reset explicitly } ToolTransformArgs ToolTransformArgs::fromXML(const QDomElement &e) { ToolTransformArgs args; int newMode = e.attribute("mode", "0").toInt(); if (newMode < 0 || newMode >= N_MODES) return ToolTransformArgs(); args.m_mode = (TransformMode) newMode; // reset explicitly args.m_editTransformPoints = false; bool result = false; if (args.m_mode == FREE_TRANSFORM || args.m_mode == PERSPECTIVE_4POINT) { QDomElement freeEl; QString filterId; result = KisDomUtils::findOnlyElement(e, "free_transform", &freeEl) && KisDomUtils::loadValue(freeEl, "transformedCenter", &args.m_transformedCenter) && KisDomUtils::loadValue(freeEl, "originalCenter", &args.m_originalCenter) && KisDomUtils::loadValue(freeEl, "rotationCenterOffset", &args.m_rotationCenterOffset) && KisDomUtils::loadValue(freeEl, "aX", &args.m_aX) && KisDomUtils::loadValue(freeEl, "aY", &args.m_aY) && KisDomUtils::loadValue(freeEl, "aZ", &args.m_aZ) && KisDomUtils::loadValue(freeEl, "cameraPos", &args.m_cameraPos) && KisDomUtils::loadValue(freeEl, "scaleX", &args.m_scaleX) && KisDomUtils::loadValue(freeEl, "scaleY", &args.m_scaleY) && KisDomUtils::loadValue(freeEl, "shearX", &args.m_shearX) && KisDomUtils::loadValue(freeEl, "shearY", &args.m_shearY) && KisDomUtils::loadValue(freeEl, "keepAspectRatio", &args.m_keepAspectRatio) && KisDomUtils::loadValue(freeEl, "flattenedPerspectiveTransform", &args.m_flattenedPerspectiveTransform) && KisDomUtils::loadValue(freeEl, "filterId", &filterId); // transformAroundRotationCenter is a new parameter introduced in Krita 4.0, // so it might be not present in older transform masks if (!KisDomUtils::loadValue(freeEl, "transformAroundRotationCenter", &args.m_transformAroundRotationCenter)) { args.m_transformAroundRotationCenter = false; } if (result) { args.m_filter = KisFilterStrategyRegistry::instance()->value(filterId); result = (bool) args.m_filter; } } else if (args.m_mode == WARP || args.m_mode == CAGE) { QDomElement warpEl; int warpType = 0; result = KisDomUtils::findOnlyElement(e, "warp_transform", &warpEl) && KisDomUtils::loadValue(warpEl, "defaultPoints", &args.m_defaultPoints) && KisDomUtils::loadValue(warpEl, "originalPoints", &args.m_origPoints) && KisDomUtils::loadValue(warpEl, "transformedPoints", &args.m_transfPoints) && KisDomUtils::loadValue(warpEl, "warpType", &warpType) && KisDomUtils::loadValue(warpEl, "alpha", &args.m_alpha); if(args.m_mode == CAGE){ // Pixel precision is a parameter introduced in Krita 4.2, so we should // expect it not being present in older files. In case it is not found, // just use the defalt value initialized by c-tor (that is, do nothing). (void) KisDomUtils::loadValue(warpEl, "pixelPrecision", &args.m_pixelPrecision); (void) KisDomUtils::loadValue(warpEl, "previewPixelPrecision", &args.m_previewPixelPrecision); } if (result && warpType >= 0 && warpType < KisWarpTransformWorker::N_MODES) { args.m_warpType = (KisWarpTransformWorker::WarpType_) warpType; } else { result = false; } } else if (args.m_mode == LIQUIFY) { QDomElement liquifyEl; result = KisDomUtils::findOnlyElement(e, "liquify_transform", &liquifyEl); *args.m_liquifyProperties = KisLiquifyProperties::fromXML(e); args.m_liquifyWorker.reset(KisLiquifyTransformWorker::fromXML(e)); } else { KIS_ASSERT_RECOVER_NOOP(0 && "Unknown transform mode"); } KIS_SAFE_ASSERT_RECOVER(result) { args = ToolTransformArgs(); } return args; } void ToolTransformArgs::saveContinuedState() { m_continuedTransformation.reset(); m_continuedTransformation.reset(new ToolTransformArgs(*this)); } void ToolTransformArgs::restoreContinuedState() { QScopedPointer tempTransformation( new ToolTransformArgs(*m_continuedTransformation)); *this = *tempTransformation; m_continuedTransformation.swap(tempTransformation); } const ToolTransformArgs* ToolTransformArgs::continuedTransform() const { return m_continuedTransformation.data(); } diff --git a/plugins/tools/tool_transform2/tool_transform_args.h b/plugins/tools/tool_transform2/tool_transform_args.h index 9cb1589950..abc6f060b3 100644 --- a/plugins/tools/tool_transform2/tool_transform_args.h +++ b/plugins/tools/tool_transform2/tool_transform_args.h @@ -1,356 +1,356 @@ /* * tool_transform_args.h - part of Krita * * 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. */ #ifndef TOOL_TRANSFORM_ARGS_H_ #define TOOL_TRANSFORM_ARGS_H_ #include #include #include #include #include "kis_liquify_properties.h" #include "kritatooltransform_export.h" #include "kis_global.h" #include "KisToolChangesTrackerData.h" #include class KisLiquifyTransformWorker; class QDomElement; /** * Class used to store the parameters of a transformation. * Some parameters are specific to free transform mode, and * others to warp mode : maybe add a union to save a little more * memory. */ class KRITATOOLTRANSFORM_EXPORT ToolTransformArgs : public KisToolChangesTrackerData { public: enum TransformMode {FREE_TRANSFORM = 0, WARP, CAGE, LIQUIFY, PERSPECTIVE_4POINT, N_MODES}; /** * Initializes the parameters for an identity transformation, * with mode set to free transform. */ ToolTransformArgs(); /** * The object return will be a copy of args. */ ToolTransformArgs(const ToolTransformArgs& args); KisToolChangesTrackerData *clone() const; /** * If mode is warp, original and transformed vector points will be of size 0. * Use setPoints method to set those vectors. */ ToolTransformArgs(TransformMode mode, QPointF transformedCenter, QPointF originalCenter, QPointF rotationCenterOffset, bool transformAroundRotationCenter, double aX, double aY, double aZ, double scaleX, double scaleY, double shearX, double shearY, KisWarpTransformWorker::WarpType warpType, double alpha, bool defaultPoints, const QString &filterId, int pixelPrecision, int previewPixelPrecision); ~ToolTransformArgs(); ToolTransformArgs& operator=(const ToolTransformArgs& args); bool operator==(const ToolTransformArgs& other) const; bool isSameMode(const ToolTransformArgs& other) const; inline TransformMode mode() const { return m_mode; } inline void setMode(TransformMode mode) { m_mode = mode; } inline int pixelPrecision() const { return m_pixelPrecision; } inline void setPixelPrecision(int precision) { m_pixelPrecision = precision; } inline int previewPixelPrecision() const { return m_previewPixelPrecision; } inline void setPreviewPixelPrecision(int precision) { m_previewPixelPrecision = precision; } //warp-related inline int numPoints() const { KIS_ASSERT_RECOVER_NOOP(m_origPoints.size() == m_transfPoints.size()); return m_origPoints.size(); } inline QPointF &origPoint(int i) { return m_origPoints[i]; } inline QPointF &transfPoint(int i) { return m_transfPoints[i]; } inline const QVector &origPoints() const { return m_origPoints; } inline const QVector &transfPoints() const { return m_transfPoints; } inline QVector &refOriginalPoints() { return m_origPoints; } inline QVector &refTransformedPoints() { return m_transfPoints; } inline KisWarpTransformWorker::WarpType warpType() const { return m_warpType; } inline double alpha() const { return m_alpha; } inline bool defaultPoints() const { return m_defaultPoints; } inline void setPoints(QVector origPoints, QVector transfPoints) { m_origPoints = QVector(origPoints); m_transfPoints = QVector(transfPoints); } inline void setWarpType(KisWarpTransformWorker::WarpType warpType) { m_warpType = warpType; } inline void setWarpCalculation(KisWarpTransformWorker::WarpCalculation warpCalc) { m_warpCalculation = warpCalc; } inline KisWarpTransformWorker::WarpCalculation warpCalculation() { return m_warpCalculation; } inline void setAlpha(double alpha) { m_alpha = alpha; } inline void setDefaultPoints(bool defaultPoints) { m_defaultPoints = defaultPoints; } //"free transform"-related inline QPointF transformedCenter() const { return m_transformedCenter; } inline QPointF originalCenter() const { return m_originalCenter; } inline QPointF rotationCenterOffset() const { return m_rotationCenterOffset; } inline bool transformAroundRotationCenter() const { return m_transformAroundRotationCenter; } inline double aX() const { return m_aX; } inline double aY() const { return m_aY; } inline double aZ() const { return m_aZ; } inline QVector3D cameraPos() const { return m_cameraPos; } inline double scaleX() const { return m_scaleX; } inline double scaleY() const { return m_scaleY; } inline bool keepAspectRatio() const { return m_keepAspectRatio; } inline double shearX() const { return m_shearX; } inline double shearY() const { return m_shearY; } inline void setTransformedCenter(QPointF transformedCenter) { m_transformedCenter = transformedCenter; } inline void setOriginalCenter(QPointF originalCenter) { m_originalCenter = originalCenter; } inline void setRotationCenterOffset(QPointF rotationCenterOffset) { m_rotationCenterOffset = rotationCenterOffset; } void setTransformAroundRotationCenter(bool value); inline void setAX(double aX) { KIS_ASSERT_RECOVER_NOOP(aX == normalizeAngle(aX)); m_aX = aX; } inline void setAY(double aY) { KIS_ASSERT_RECOVER_NOOP(aY == normalizeAngle(aY)); m_aY = aY; } inline void setAZ(double aZ) { KIS_ASSERT_RECOVER_NOOP(aZ == normalizeAngle(aZ)); m_aZ = aZ; } inline void setCameraPos(const QVector3D &pos) { m_cameraPos = pos; } inline void setScaleX(double scaleX) { m_scaleX = scaleX; } inline void setScaleY(double scaleY) { m_scaleY = scaleY; } inline void setKeepAspectRatio(bool value) { m_keepAspectRatio = value; } inline void setShearX(double shearX) { m_shearX = shearX; } inline void setShearY(double shearY) { m_shearY = shearY; } inline QString filterId() const { return m_filter->id(); } void setFilterId(const QString &id); inline KisFilterStrategy* filter() const { return m_filter; } bool isIdentity() const; inline QTransform flattenedPerspectiveTransform() const { return m_flattenedPerspectiveTransform; } inline void setFlattenedPerspectiveTransform(const QTransform &value) { m_flattenedPerspectiveTransform = value; } bool isEditingTransformPoints() const { return m_editTransformPoints; } void setEditingTransformPoints(bool value) { m_editTransformPoints = value; } const KisLiquifyProperties* liquifyProperties() const { return m_liquifyProperties.data(); } KisLiquifyProperties* liquifyProperties() { return m_liquifyProperties.data(); } void initLiquifyTransformMode(const QRect &srcRect); void saveLiquifyTransformMode() const; KisLiquifyTransformWorker* liquifyWorker() const { return m_liquifyWorker.data(); } void toXML(QDomElement *e) const; static ToolTransformArgs fromXML(const QDomElement &e); void translate(const QPointF &offset); void saveContinuedState(); void restoreContinuedState(); const ToolTransformArgs* continuedTransform() const; private: void clear(); void init(const ToolTransformArgs& args); - TransformMode m_mode; + TransformMode m_mode {ToolTransformArgs::TransformMode::FREE_TRANSFORM}; // warp-related arguments // these are basically the arguments taken by the warp transform worker - bool m_defaultPoints; // true : the original points are set to make a grid + bool m_defaultPoints {true}; // true : the original points are set to make a grid // which density is given by numPoints() QVector m_origPoints; QVector m_transfPoints; - KisWarpTransformWorker::WarpType m_warpType; - KisWarpTransformWorker::WarpCalculation m_warpCalculation; // DRAW or GRID - double m_alpha; + KisWarpTransformWorker::WarpType m_warpType {KisWarpTransformWorker::WarpType_::RIGID_TRANSFORM}; + KisWarpTransformWorker::WarpCalculation m_warpCalculation {KisWarpTransformWorker::WarpCalculation::DRAW}; // DRAW or GRID + double m_alpha {1.0}; //'free transform'-related // basically the arguments taken by the transform worker QPointF m_transformedCenter; QPointF m_originalCenter; QPointF m_rotationCenterOffset; // the position of the rotation center relative to // the original top left corner of the selection // before any transformation - bool m_transformAroundRotationCenter; // In freehand mode makes the scaling and other transformations + bool m_transformAroundRotationCenter {false}; // In freehand mode makes the scaling and other transformations // be anchored to the rotation center point. - double m_aX; - double m_aY; - double m_aZ; + double m_aX {0}; + double m_aY {0}; + double m_aZ {0}; QVector3D m_cameraPos {QVector3D(0,0,1024)}; - double m_scaleX; - double m_scaleY; - double m_shearX; - double m_shearY; + double m_scaleX {1.0}; + double m_scaleY {1.0}; + double m_shearX {0.0}; + double m_shearY {0.0}; bool m_keepAspectRatio {false}; // perspective trasform related QTransform m_flattenedPerspectiveTransform; - KisFilterStrategy *m_filter; + KisFilterStrategy *m_filter {0}; bool m_editTransformPoints {false}; QSharedPointer m_liquifyProperties; QScopedPointer m_liquifyWorker; /** * When we continue a transformation, m_continuedTransformation * stores the initial step of our transform. All cancel and revert * operations should revert to it. */ QScopedPointer m_continuedTransformation; //PixelPrecision should always be in powers of 2 - int m_pixelPrecision; - int m_previewPixelPrecision; + int m_pixelPrecision {8}; + int m_previewPixelPrecision {16}; }; #endif // TOOL_TRANSFORM_ARGS_H_ diff --git a/sdk/tests/filestest.h b/sdk/tests/filestest.h index f4ff0db9f1..0b8babccd6 100644 --- a/sdk/tests/filestest.h +++ b/sdk/tests/filestest.h @@ -1,378 +1,377 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef FILESTEST #define FILESTEST #include "testutil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace TestUtil { void testFiles(const QString& _dirname, const QStringList& exclusions, const QString &resultSuffix = QString(), int fuzzy = 0, int maxNumFailingPixels = 0, bool showDebug = true) { QDir dirSources(_dirname); QStringList failuresFileInfo; QStringList failuresDocImage; QStringList failuresCompare; Q_FOREACH (QFileInfo sourceFileInfo, dirSources.entryInfoList()) { qDebug() << sourceFileInfo.fileName(); if (exclusions.indexOf(sourceFileInfo.fileName()) > -1) { continue; } if (!sourceFileInfo.isHidden() && !sourceFileInfo.isDir()) { QFileInfo resultFileInfo(QString(FILES_DATA_DIR) + "/results/" + sourceFileInfo.fileName() + resultSuffix + ".png"); if (!resultFileInfo.exists()) { failuresFileInfo << resultFileInfo.fileName(); continue; } KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); KisImportExportManager manager(doc); doc->setFileBatchMode(true); KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString()); Q_UNUSED(status); if (!doc->image()) { failuresDocImage << sourceFileInfo.fileName(); continue; } QString id = doc->image()->colorSpace()->id(); if (id != "GRAYA" && id != "GRAYAU16" && id != "RGBA" && id != "RGBA16") { dbgKrita << "Images need conversion"; doc->image()->convertImageColorSpace(KoColorSpaceRegistry::instance()->rgb8(), KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::NoOptimization); doc->image()->waitForDone(); } qApp->processEvents(); doc->image()->waitForDone(); QImage sourceImage = doc->image()->projection()->convertToQImage(0, doc->image()->bounds()); QImage resultImage(resultFileInfo.absoluteFilePath()); resultImage = resultImage.convertToFormat(QImage::Format_ARGB32); sourceImage = sourceImage.convertToFormat(QImage::Format_ARGB32); QPoint pt; if (!TestUtil::compareQImages(pt, resultImage, sourceImage, fuzzy, fuzzy, maxNumFailingPixels, showDebug)) { failuresCompare << sourceFileInfo.fileName() + ": " + QString("Pixel (%1,%2) has different values").arg(pt.x()).arg(pt.y()).toLatin1(); sourceImage.save(sourceFileInfo.fileName() + ".png"); resultImage.save(resultFileInfo.fileName() + ".expected.png"); continue; } delete doc; } } if (failuresCompare.isEmpty() && failuresDocImage.isEmpty() && failuresFileInfo.isEmpty()) { return; } qWarning() << "Comparison failures: " << failuresCompare; qWarning() << "No image failures: " << failuresDocImage; qWarning() << "No comparison image: " << failuresFileInfo; QFAIL("Failed testing files"); } void prepareFile(QFileInfo sourceFileInfo, bool removePermissionToWrite, bool removePermissionToRead) { QFileDevice::Permissions permissionsBefore; if (sourceFileInfo.exists()) { permissionsBefore = QFile::permissions(sourceFileInfo.absoluteFilePath()); ENTER_FUNCTION() << permissionsBefore; } else { QFile file(sourceFileInfo.absoluteFilePath()); bool opened = file.open(QIODevice::ReadWrite); if (!opened) { qDebug() << "The file cannot be opened/created: " << file.error() << file.errorString(); } permissionsBefore = file.permissions(); file.close(); } QFileDevice::Permissions permissionsNow = permissionsBefore; if (removePermissionToRead) { permissionsNow = permissionsBefore & (~QFileDevice::ReadUser & ~QFileDevice::ReadOwner & ~QFileDevice::ReadGroup & ~QFileDevice::ReadOther); } if (removePermissionToWrite) { permissionsNow = permissionsBefore & (~QFileDevice::WriteUser & ~QFileDevice::WriteOwner & ~QFileDevice::WriteGroup & ~QFileDevice::WriteOther); } QFile::setPermissions(sourceFileInfo.absoluteFilePath(), permissionsNow); } void restorePermissionsToReadAndWrite(QFileInfo sourceFileInfo) { QFileDevice::Permissions permissionsNow = sourceFileInfo.permissions(); QFileDevice::Permissions permissionsAfter = permissionsNow | (QFileDevice::ReadUser | QFileDevice::ReadOwner | QFileDevice::ReadGroup | QFileDevice::ReadOther) | (QFileDevice::WriteUser | QFileDevice::WriteOwner | QFileDevice::WriteGroup | QFileDevice::WriteOther); QFile::setPermissions(sourceFileInfo.absoluteFilePath(), permissionsAfter); } void testImportFromWriteonly(const QString& _dirname, QString mimetype = "") { QString writeonlyFilename = _dirname + "writeonlyFile.txt"; QFileInfo sourceFileInfo(writeonlyFilename); prepareFile(sourceFileInfo, false, true); KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); KisImportExportManager manager(doc); doc->setFileBatchMode(true); KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), mimetype); qDebug() << "import result = " << status; QString failMessage = ""; bool fail = false; if (status == ImportExportCodes::FileFormatIncorrect) { qDebug() << "Make sure you set the correct mimetype in the test case."; failMessage = "Incorrect status."; fail = true; } qApp->processEvents(); if (doc->image()) { doc->image()->waitForDone(); } delete doc; restorePermissionsToReadAndWrite(sourceFileInfo); QVERIFY(!status.isOk()); if (fail) { QFAIL(failMessage.toUtf8()); } } void testExportToReadonly(const QString& _dirname, QString mimetype = "", bool useDocumentExport=false) { QString readonlyFilename = _dirname + "readonlyFile.txt"; QFileInfo sourceFileInfo(readonlyFilename); prepareFile(sourceFileInfo, true, false); KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); KisImportExportManager manager(doc); doc->setFileBatchMode(true); KisImportExportErrorCode status = ImportExportCodes::OK; QString failMessage = ""; bool fail = false; { MaskParent p; ENTER_FUNCTION() << doc->image(); doc->setCurrentImage(p.image); if (useDocumentExport) { bool result = doc->exportDocumentSync(QUrl(sourceFileInfo.absoluteFilePath()), mimetype.toUtf8()); status = result ? ImportExportCodes::OK : ImportExportCodes::Failure; } else { status = manager.exportDocument(sourceFileInfo.absoluteFilePath(), sourceFileInfo.absoluteFilePath(), mimetype.toUtf8()); } qDebug() << "export result = " << status; if (!useDocumentExport && status == ImportExportCodes::FileFormatIncorrect) { qDebug() << "Make sure you set the correct mimetype in the test case."; failMessage = "Incorrect status."; fail = true; } qApp->processEvents(); if (doc->image()) { doc->image()->waitForDone(); } } delete doc; restorePermissionsToReadAndWrite(sourceFileInfo); QVERIFY(!status.isOk()); if (fail) { QFAIL(failMessage.toUtf8()); } } void testImportIncorrectFormat(const QString& _dirname, QString mimetype = "") { QString incorrectFormatFilename = _dirname + "incorrectFormatFile.txt"; QFileInfo sourceFileInfo(incorrectFormatFilename); prepareFile(sourceFileInfo, false, false); KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); KisImportExportManager manager(doc); doc->setFileBatchMode(true); KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), mimetype); qDebug() << "import result = " << status; - qApp->processEvents(); if (doc->image()) { doc->image()->waitForDone(); } delete doc; QVERIFY(!status.isOk()); QVERIFY(status == KisImportExportErrorCode(ImportExportCodes::FileFormatIncorrect) || status == KisImportExportErrorCode(ImportExportCodes::ErrorWhileReading)); // in case the filter doesn't know if it can't read or just parse } void testExportToColorSpace(const QString& _dirname, QString mimetype, const KoColorSpace* space, KisImportExportErrorCode expected, bool useDocumentExport=false) { QString colorspaceFilename = _dirname + "colorspace.txt"; QFileInfo sourceFileInfo(colorspaceFilename); prepareFile(sourceFileInfo, true, true); restorePermissionsToReadAndWrite(sourceFileInfo); KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); KisImportExportManager manager(doc); doc->setFileBatchMode(true); KisImportExportErrorCode statusExport = ImportExportCodes::OK; KisImportExportErrorCode statusImport = ImportExportCodes::OK; QString failMessage = ""; bool fail = false; { MaskParent p; doc->setCurrentImage(p.image); doc->image()->convertImageColorSpace(space, KoColorConversionTransformation::Intent::IntentPerceptual, KoColorConversionTransformation::ConversionFlag::Empty); doc->image()->waitForDone(); if (useDocumentExport) { bool result = doc->exportDocumentSync(QUrl(QString("file:") + QString(colorspaceFilename)), mimetype.toUtf8()); statusExport = result ? ImportExportCodes::OK : ImportExportCodes::Failure; } else { statusExport = manager.exportDocument(colorspaceFilename, colorspaceFilename, mimetype.toUtf8()); } statusImport = manager.importDocument(colorspaceFilename, mimetype.toUtf8()); if (!(statusImport == ImportExportCodes::OK)) { fail = true; failMessage = "Incorrect status"; } bool mismatch = (*(doc->image()->colorSpace()) != *space) || (doc->image()->colorSpace()->profile() != space->profile()); if (mismatch) { qDebug() << "Document color space = " << (doc->image()->colorSpace())->id(); qDebug() << "Saved color space = " << space->id(); fail = true; failMessage = "Mismatch of color spaces"; } if (!useDocumentExport && statusExport == ImportExportCodes::FileFormatIncorrect) { qDebug() << "Make sure you set the correct mimetype in the test case."; failMessage = "Incorrect status."; fail = true; } qApp->processEvents(); if (doc->image()) { doc->image()->waitForDone(); } } delete doc; QFile::remove(colorspaceFilename); if (fail) { QFAIL(failMessage.toUtf8()); } QVERIFY(statusExport.isOk()); if (!useDocumentExport) { QVERIFY(statusExport == expected); } } } #endif